[{"data":1,"prerenderedAt":3099},["ShallowReactive",2],{"post-building-brew-haven-ab-testing-my-coffee-shop-dreams-with-devcycle":3},{"id":4,"title":5,"body":6,"canonicalUrl":3082,"cover":3083,"date":3084,"description":3085,"draft":3086,"extension":3087,"hashnodeId":3088,"meta":3089,"navigation":290,"path":3090,"seo":3091,"slug":3092,"stem":3092,"tags":3093,"__hash__":3098},"posts\u002Fbuilding-brew-haven-ab-testing-my-coffee-shop-dreams-with-devcycle.md","Building Brew Haven: A\u002FB Testing My Coffee Shop Dreams with DevCycle",{"type":7,"value":8,"toc":3067},"minimark",[9,13,16,22,27,30,35,38,42,52,62,66,69,85,89,97,100,111,114,118,121,205,208,212,215,235,238,242,245,248,252,267,689,696,905,908,912,923,926,1439,1442,2101,2104,2308,2311,2817,2820,3008,3019,3023,3026,3029,3043,3046,3049,3052,3055,3063],[10,11,12],"p",{},"Do you love coffee? As developers, many of us jokingly claim to be \"powered by coffee\", and the thought of opening a quaint coffee shop someday often lingers in the back of our minds — perhaps as a post-dev career dream.",[10,14,15],{},"When brainstorming ideas for a fun project to showcase feature flags, this coffee shop fantasy kept nudging me: \"Pick me! Build me!\". So, I decided to indulge that thought and create Brew Haven, a dummy coffee shop app that explores the power of feature flags.",[17,18,19],"blockquote",{},[10,20,21],{},"This project was originally created as part of a dev challenge to showcase the power of feature flags in an engaging way.",[23,24,26],"h2",{"id":25},"project-overview","Project Overview",[10,28,29],{},"As you might have guessed, I built a playground to explore the power of feature flags, packaged as a coffee shop app. The app features customizable menus, seasonal items, and dynamic A\u002FB testing for promotions using the DevCycle SDK. It also leverages the DevCycle Management APIs to power an admin panel, giving shop managers full control over these features in real time.",[31,32,34],"h3",{"id":33},"demo","Demo",[10,36,37],{},"The following video gives a short walkthrough of the app.",[39,40],"media-embed",{"url":41},"https:\u002F\u002Fyoutu.be\u002FXp6i7GXl4hM",[10,43,44,45],{},"You can try out the live app here: ",[46,47,51],"a",{"href":48,"rel":49},"https:\u002F\u002Fbrewwhaven.netlify.app",[50],"nofollow","Brew Haven",[17,53,54],{},[10,55,56,57,61],{},"Note: The admin page uses a dummy password auth. Use ",[58,59,60],"code",{},"admin@123"," passowrd to view the admin panel and play around with the available feature flags.",[31,63,65],{"id":64},"technologies-used","Technologies Used",[10,67,68],{},"To bring Brew Haven to life, I used the following:",[70,71,72,76,79,82],"ul",{},[73,74,75],"li",{},"React with Vite for a fast and modular frontend.",[73,77,78],{},"Shadcn UI for styling and components.",[73,80,81],{},"Netlify Functions for secure serverless DevCycle Management API calls.",[73,83,84],{},"DevCycle SDK for feature flag integration on the client side.",[23,86,88],{"id":87},"what-is-devcycle","What is DevCycle?",[10,90,91,92,96],{},"DevCycle is a feature management platform that helps developers experiment with new features, run A\u002FB tests, and gradually roll out updates without downtime. It uses ",[93,94,95],"strong",{},"feature flags","—switches in your code that let you turn specific features on or off in real time.",[10,98,99],{},"With DevCycle, you can:",[70,101,102,105,108],{},[73,103,104],{},"Deploy features safely using controlled rollouts.",[73,106,107],{},"Customize user experiences through A\u002FB testing.",[73,109,110],{},"Quickly respond to user feedback without redeploying your code.",[10,112,113],{},"DevCycle integrates seamlessly into your development process, making feature management simple and efficient. In Brew Haven, I used it to power dynamic menu customization, promotions, and more.",[31,115,117],{"id":116},"how-does-devcycle-work","How Does DevCycle Work?",[10,119,120],{},"DevCycle is a powerful tool designed to simplify the management of feature flags and provide advanced functionality for tailoring your software’s behavior. Here’s an overview of how it works:",[122,123,124,163,197],"ol",{},[73,125,126,129,132,133,136,137],{},[93,127,128],{},"Feature Flags and Feature Types",[130,131],"br",{},"\nAt the heart of DevCycle are ",[93,134,135],{},"features",", which can have one or more toggles (or flags). Features are categorized into four types, tailored for specific use cases:",[70,138,139,145,151,157],{},[73,140,141,144],{},[93,142,143],{},"Release",": Designed for separating code deployment from feature release. This enables merging incomplete or in-progress code into production without affecting end users until it’s toggled on.",[73,146,147,150],{},[93,148,149],{},"Ops",": Helps ensure system safety during feature rollouts, with built-in mechanisms for gradual rollouts or emergency kill switches.",[73,152,153,156],{},[93,154,155],{},"Experiment",": Ideal for A\u002FB or multivariate testing. It lets you distribute users across variations and track outcomes to make data-driven decisions.",[73,158,159,162],{},[93,160,161],{},"Permission",": Gating features based on user attributes, such as subscription plans or roles, allowing granular control over feature access.",[73,164,165,168,170,171,174,175,178,179,182,183,185,188,189],{},[93,166,167],{},"Variables and Variations",[130,169],{},"\nEach feature flag can have associated ",[93,172,173],{},"variables",", representing configurable values. For example, a flag for a promotion might include variables like ",[58,176,177],{},"discountPercentage"," or ",[58,180,181],{},"minimumOrderValue",".",[130,184],{},[93,186,187],{},"Variations"," are pre-defined combinations of variable values, which dictate how the feature behaves. For instance:",[70,190,191,194],{},[73,192,193],{},"Variation A might offer a 10% discount.",[73,195,196],{},"Variation B might provide a 15% discount with a higher order value threshold.",[73,198,199,202,204],{},[93,200,201],{},"Targeting Rules and Rollouts",[130,203],{},"\nDevCycle allows you to define rules that control which users see specific variations. These rules can be based on user properties (like location or plan type) or random distribution for A\u002FB testing. Features can also be rolled out gradually, allowing you to monitor performance and ensure stability before scaling.",[10,206,207],{},"This structured approach ensures safe experimentation, seamless feature rollouts, and precise customization—all with minimal risk to your users’ experience.",[31,209,211],{"id":210},"feature-flags-in-brew-haven","Feature Flags in Brew Haven",[10,213,214],{},"Brew Haven uses the following feature flags to make the app dynamic and customizable:",[122,216,217,223,229],{},[73,218,219,222],{},[93,220,221],{},"Coffee Menu",": Feature flags control menu customization, seasonal items, and order personalization.",[73,224,225,228],{},[93,226,227],{},"Payment & Ordering",": Enable or disable online payments, loyalty points, and live order tracking with simple toggles.",[73,230,231,234],{},[93,232,233],{},"A\u002FB Testing Promotions",": Run experiments on discounts and promotional offers to optimize customer engagement.",[10,236,237],{},"The first two are release type feature flags, while the last one is an experiment type feature. These flags not only made development faster but also showcased the versatility of DevCycle.",[23,239,241],{"id":240},"integrating-devcycle","Integrating DevCycle",[10,243,244],{},"The source code of the app is open source, and is available on GitHub. We’ll briefly look at how to integrate and use DevCycle in a React App.",[39,246],{"url":247},"https:\u002F\u002Fgithub.com\u002Fra-jeev\u002Fbrew-haven",[31,249,251],{"id":250},"client-side-usage-of-devcycle-sdk","Client Side Usage of DevCycle SDK",[10,253,254,255,258,259,262,263,266],{},"After adding the ",[58,256,257],{},"DevCycle React SDK"," dependency, we first initialize the ",[58,260,261],{},"DevCycleProvider"," in the ",[58,264,265],{},"App.tsx"," file as shown below:",[268,269,274],"pre",{"className":270,"code":271,"language":272,"meta":273,"style":273},"language-typescript shiki shiki-themes github-light github-dark","\u002F\u002F src\u002FApp.tsx\n\nimport { withDevCycleProvider } from \"@devcycle\u002Freact-client-sdk\";\n\u002F\u002F ...\n\nfunction App() {\n  return (\n    \u003CThemeProvider defaultTheme=\"system\" storageKey=\"coffee-shop-ui-theme\">\n      \u003CRouter>\n        \u003CLayout>\n          \u003CRoutes>\n            \u003CRoute path=\"\u002F\" element={\u003CHome \u002F>} \u002F>\n            \u003CRoute path=\"\u002Fmenu\" element={\u003CMenu \u002F>} \u002F>\n            \u003CRoute path=\"\u002Fcheckout\" element={\u003CCheckout \u002F>} \u002F>\n            \u003CRoute path=\"\u002Forders\" element={\u003COrders \u002F>} \u002F>\n            \u003CRoute\n              path=\"\u002Fadmin\"\n              element={\n                \u003CProtectedRoute>\n                  \u003CAdmin \u002F>\n                \u003C\u002FProtectedRoute>\n              }\n            \u002F>\n          \u003C\u002FRoutes>\n        \u003C\u002FLayout>\n      \u003C\u002FRouter>\n      \u003CToaster \u002F>\n    \u003C\u002FThemeProvider>\n  );\n}\n\nexport default withDevCycleProvider({\n  sdkKey: import.meta.env.VITE_DEVCYCLE_CLIENT_SDK_KEY,\n  options: {\n    logLevel: \"debug\",\n  },\n})(App);\n","typescript","",[58,275,276,285,292,313,319,324,337,346,372,383,394,405,430,451,472,493,502,513,524,530,536,542,548,554,564,574,584,594,605,611,617,622,637,660,666,677,683],{"__ignoreMap":273},[277,278,281],"span",{"class":279,"line":280},"line",1,[277,282,284],{"class":283},"sJ8bj","\u002F\u002F src\u002FApp.tsx\n",[277,286,288],{"class":279,"line":287},2,[277,289,291],{"emptyLinePlaceholder":290},true,"\n",[277,293,295,299,303,306,310],{"class":279,"line":294},3,[277,296,298],{"class":297},"szBVR","import",[277,300,302],{"class":301},"sVt8B"," { withDevCycleProvider } ",[277,304,305],{"class":297},"from",[277,307,309],{"class":308},"sZZnC"," \"@devcycle\u002Freact-client-sdk\"",[277,311,312],{"class":301},";\n",[277,314,316],{"class":279,"line":315},4,[277,317,318],{"class":283},"\u002F\u002F ...\n",[277,320,322],{"class":279,"line":321},5,[277,323,291],{"emptyLinePlaceholder":290},[277,325,327,330,334],{"class":279,"line":326},6,[277,328,329],{"class":297},"function",[277,331,333],{"class":332},"sScJk"," App",[277,335,336],{"class":301},"() {\n",[277,338,340,343],{"class":279,"line":339},7,[277,341,342],{"class":297},"  return",[277,344,345],{"class":301}," (\n",[277,347,349,352,355,358,361,364,366,369],{"class":279,"line":348},8,[277,350,351],{"class":297},"    \u003C",[277,353,354],{"class":301},"ThemeProvider defaultTheme",[277,356,357],{"class":297},"=",[277,359,360],{"class":308},"\"system\"",[277,362,363],{"class":301}," storageKey",[277,365,357],{"class":297},[277,367,368],{"class":308},"\"coffee-shop-ui-theme\"",[277,370,371],{"class":297},">\n",[277,373,375,378,381],{"class":279,"line":374},9,[277,376,377],{"class":301},"      \u003C",[277,379,380],{"class":332},"Router",[277,382,371],{"class":301},[277,384,386,389,392],{"class":279,"line":385},10,[277,387,388],{"class":301},"        \u003C",[277,390,391],{"class":332},"Layout",[277,393,371],{"class":301},[277,395,397,400,403],{"class":279,"line":396},11,[277,398,399],{"class":301},"          \u003C",[277,401,402],{"class":332},"Routes",[277,404,371],{"class":301},[277,406,408,411,414,416,419,422,424,427],{"class":279,"line":407},12,[277,409,410],{"class":297},"            \u003C",[277,412,413],{"class":301},"Route path",[277,415,357],{"class":297},[277,417,418],{"class":308},"\"\u002F\"",[277,420,421],{"class":301}," element",[277,423,357],{"class":297},[277,425,426],{"class":301},"{\u003CHome \u002F>} ",[277,428,429],{"class":297},"\u002F>\n",[277,431,433,435,437,439,442,444,446,449],{"class":279,"line":432},13,[277,434,410],{"class":297},[277,436,413],{"class":301},[277,438,357],{"class":297},[277,440,441],{"class":308},"\"\u002Fmenu\"",[277,443,421],{"class":301},[277,445,357],{"class":297},[277,447,448],{"class":301},"{\u003CMenu \u002F>} ",[277,450,429],{"class":297},[277,452,454,456,458,460,463,465,467,470],{"class":279,"line":453},14,[277,455,410],{"class":297},[277,457,413],{"class":301},[277,459,357],{"class":297},[277,461,462],{"class":308},"\"\u002Fcheckout\"",[277,464,421],{"class":301},[277,466,357],{"class":297},[277,468,469],{"class":301},"{\u003CCheckout \u002F>} ",[277,471,429],{"class":297},[277,473,475,477,479,481,484,486,488,491],{"class":279,"line":474},15,[277,476,410],{"class":297},[277,478,413],{"class":301},[277,480,357],{"class":297},[277,482,483],{"class":308},"\"\u002Forders\"",[277,485,421],{"class":301},[277,487,357],{"class":297},[277,489,490],{"class":301},"{\u003COrders \u002F>} ",[277,492,429],{"class":297},[277,494,496,498],{"class":279,"line":495},16,[277,497,410],{"class":297},[277,499,501],{"class":500},"s4XuR","Route\n",[277,503,505,508,510],{"class":279,"line":504},17,[277,506,507],{"class":301},"              path",[277,509,357],{"class":297},[277,511,512],{"class":308},"\"\u002Fadmin\"\n",[277,514,516,519,521],{"class":279,"line":515},18,[277,517,518],{"class":301},"              element",[277,520,357],{"class":297},[277,522,523],{"class":301},"{\n",[277,525,527],{"class":279,"line":526},19,[277,528,529],{"class":301},"                \u003CProtectedRoute>\n",[277,531,533],{"class":279,"line":532},20,[277,534,535],{"class":301},"                  \u003CAdmin \u002F>\n",[277,537,539],{"class":279,"line":538},21,[277,540,541],{"class":301},"                \u003C\u002FProtectedRoute>\n",[277,543,545],{"class":279,"line":544},22,[277,546,547],{"class":301},"              }\n",[277,549,551],{"class":279,"line":550},23,[277,552,553],{"class":297},"            \u002F>\n",[277,555,557,560,562],{"class":279,"line":556},24,[277,558,559],{"class":297},"          \u003C\u002F",[277,561,402],{"class":301},[277,563,371],{"class":297},[277,565,567,570,572],{"class":279,"line":566},25,[277,568,569],{"class":297},"        \u003C\u002F",[277,571,391],{"class":301},[277,573,371],{"class":297},[277,575,577,580,582],{"class":279,"line":576},26,[277,578,579],{"class":297},"      \u003C\u002F",[277,581,380],{"class":301},[277,583,371],{"class":297},[277,585,587,589,592],{"class":279,"line":586},27,[277,588,377],{"class":297},[277,590,591],{"class":301},"Toaster ",[277,593,429],{"class":297},[277,595,597,600,603],{"class":279,"line":596},28,[277,598,599],{"class":297},"    \u003C\u002F",[277,601,602],{"class":301},"ThemeProvider",[277,604,371],{"class":297},[277,606,608],{"class":279,"line":607},29,[277,609,610],{"class":301},"  );\n",[277,612,614],{"class":279,"line":613},30,[277,615,616],{"class":301},"}\n",[277,618,620],{"class":279,"line":619},31,[277,621,291],{"emptyLinePlaceholder":290},[277,623,625,628,631,634],{"class":279,"line":624},32,[277,626,627],{"class":297},"export",[277,629,630],{"class":297}," default",[277,632,633],{"class":332}," withDevCycleProvider",[277,635,636],{"class":301},"({\n",[277,638,640,643,645,647,651,654,657],{"class":279,"line":639},33,[277,641,642],{"class":301},"  sdkKey: ",[277,644,298],{"class":297},[277,646,182],{"class":301},[277,648,650],{"class":649},"sj4cs","meta",[277,652,653],{"class":301},".env.",[277,655,656],{"class":649},"VITE_DEVCYCLE_CLIENT_SDK_KEY",[277,658,659],{"class":301},",\n",[277,661,663],{"class":279,"line":662},34,[277,664,665],{"class":301},"  options: {\n",[277,667,669,672,675],{"class":279,"line":668},35,[277,670,671],{"class":301},"    logLevel: ",[277,673,674],{"class":308},"\"debug\"",[277,676,659],{"class":301},[277,678,680],{"class":279,"line":679},36,[277,681,682],{"class":301},"  },\n",[277,684,686],{"class":279,"line":685},37,[277,687,688],{"class":301},"})(App);\n",[10,690,691,692,695],{},"And then we create a ",[58,693,694],{},"useFeatureFlags"," hook to get the various feature flags variables associated with the app as shown below:",[268,697,699],{"className":270,"code":698,"language":272,"meta":273,"style":273},"\u002F\u002F src\u002Fhooks\u002Fuse-feature-flags.ts\n\nimport { useVariableValue } from \"@devcycle\u002Freact-client-sdk\";\nimport { featureKeys } from \"@\u002Flib\u002Fconsts\";\n\nexport function useFeatureFlags() {\n  const showNutritionInfo = useVariableValue(\n    featureKeys.SHOW_NUTRITION_INFO,\n    false,\n  );\n  const enableOnlinePayment = useVariableValue(\n    featureKeys.ENABLE_ONLINE_PAYMENT,\n    false,\n  );\n  const showPromotionalBanner = useVariableValue(\n    featureKeys.SHOW_PROMOTIONAL_BANNER,\n    \"\",\n  );\n\n  \u002F\u002F etc.\n\n  return {\n    showNutritionInfo,\n    enableOnlinePayment,\n    showPromotionalBanner,\n    \u002F\u002F ...\n  };\n}\n",[58,700,701,706,710,723,737,741,753,770,780,787,791,804,813,819,823,836,845,852,856,860,865,869,876,881,886,891,896,901],{"__ignoreMap":273},[277,702,703],{"class":279,"line":280},[277,704,705],{"class":283},"\u002F\u002F src\u002Fhooks\u002Fuse-feature-flags.ts\n",[277,707,708],{"class":279,"line":287},[277,709,291],{"emptyLinePlaceholder":290},[277,711,712,714,717,719,721],{"class":279,"line":294},[277,713,298],{"class":297},[277,715,716],{"class":301}," { useVariableValue } ",[277,718,305],{"class":297},[277,720,309],{"class":308},[277,722,312],{"class":301},[277,724,725,727,730,732,735],{"class":279,"line":315},[277,726,298],{"class":297},[277,728,729],{"class":301}," { featureKeys } ",[277,731,305],{"class":297},[277,733,734],{"class":308}," \"@\u002Flib\u002Fconsts\"",[277,736,312],{"class":301},[277,738,739],{"class":279,"line":321},[277,740,291],{"emptyLinePlaceholder":290},[277,742,743,745,748,751],{"class":279,"line":326},[277,744,627],{"class":297},[277,746,747],{"class":297}," function",[277,749,750],{"class":332}," useFeatureFlags",[277,752,336],{"class":301},[277,754,755,758,761,764,767],{"class":279,"line":339},[277,756,757],{"class":297},"  const",[277,759,760],{"class":649}," showNutritionInfo",[277,762,763],{"class":297}," =",[277,765,766],{"class":332}," useVariableValue",[277,768,769],{"class":301},"(\n",[277,771,772,775,778],{"class":279,"line":348},[277,773,774],{"class":301},"    featureKeys.",[277,776,777],{"class":649},"SHOW_NUTRITION_INFO",[277,779,659],{"class":301},[277,781,782,785],{"class":279,"line":374},[277,783,784],{"class":649},"    false",[277,786,659],{"class":301},[277,788,789],{"class":279,"line":385},[277,790,610],{"class":301},[277,792,793,795,798,800,802],{"class":279,"line":396},[277,794,757],{"class":297},[277,796,797],{"class":649}," enableOnlinePayment",[277,799,763],{"class":297},[277,801,766],{"class":332},[277,803,769],{"class":301},[277,805,806,808,811],{"class":279,"line":407},[277,807,774],{"class":301},[277,809,810],{"class":649},"ENABLE_ONLINE_PAYMENT",[277,812,659],{"class":301},[277,814,815,817],{"class":279,"line":432},[277,816,784],{"class":649},[277,818,659],{"class":301},[277,820,821],{"class":279,"line":453},[277,822,610],{"class":301},[277,824,825,827,830,832,834],{"class":279,"line":474},[277,826,757],{"class":297},[277,828,829],{"class":649}," showPromotionalBanner",[277,831,763],{"class":297},[277,833,766],{"class":332},[277,835,769],{"class":301},[277,837,838,840,843],{"class":279,"line":495},[277,839,774],{"class":301},[277,841,842],{"class":649},"SHOW_PROMOTIONAL_BANNER",[277,844,659],{"class":301},[277,846,847,850],{"class":279,"line":504},[277,848,849],{"class":308},"    \"\"",[277,851,659],{"class":301},[277,853,854],{"class":279,"line":515},[277,855,610],{"class":301},[277,857,858],{"class":279,"line":526},[277,859,291],{"emptyLinePlaceholder":290},[277,861,862],{"class":279,"line":532},[277,863,864],{"class":283},"  \u002F\u002F etc.\n",[277,866,867],{"class":279,"line":538},[277,868,291],{"emptyLinePlaceholder":290},[277,870,871,873],{"class":279,"line":544},[277,872,342],{"class":297},[277,874,875],{"class":301}," {\n",[277,877,878],{"class":279,"line":550},[277,879,880],{"class":301},"    showNutritionInfo,\n",[277,882,883],{"class":279,"line":556},[277,884,885],{"class":301},"    enableOnlinePayment,\n",[277,887,888],{"class":279,"line":566},[277,889,890],{"class":301},"    showPromotionalBanner,\n",[277,892,893],{"class":279,"line":576},[277,894,895],{"class":283},"    \u002F\u002F ...\n",[277,897,898],{"class":279,"line":586},[277,899,900],{"class":301},"  };\n",[277,902,903],{"class":279,"line":596},[277,904,616],{"class":301},[10,906,907],{},"This hook is used in the app to toggle various code sections on\u002Foff to change the UI.",[31,909,911],{"id":910},"interacting-with-devcycle-management-apis","Interacting with DevCycle Management APIs",[10,913,914,915,918,919,922],{},"For securely calling the Management APIs, we use Netlify serverless functions so that the DevCycle API's ",[58,916,917],{},"client_id"," and ",[58,920,921],{},"client_secret"," are not exposed to the client.",[10,924,925],{},"Before we can interact with the DevCycle APIs, we need to generate an auth token using the DevCycle APIs client id and secret. The below code shows how to generate the auth token:",[268,927,931],{"className":928,"code":929,"language":930,"meta":273,"style":273},"language-ts shiki shiki-themes github-light github-dark","\u002F\u002F netlify\u002Ffunctions\u002Ffeature-flags.mts\n\ninterface AuthToken {\n  access_token: string;\n  expires_in: number;\n  token_type: string;\n}\n\nlet tokenCache: { token: string; expiresAt: number } | null = null;\n\nasync function getAuthToken() {\n  if (tokenCache && tokenCache.expiresAt > Date.now()) {\n    return tokenCache.token;\n  }\n\n  try {\n    const response = await fetch(\"https:\u002F\u002Fauth.devcycle.com\u002Foauth\u002Ftoken\", {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application\u002Fx-www-form-urlencoded\",\n      },\n      body: new URLSearchParams({\n        grant_type: \"client_credentials\",\n        audience: \"https:\u002F\u002Fapi.devcycle.com\u002F\",\n        client_id: process.env.DEVCYCLE_API_CLIENT_ID!,\n        client_secret: process.env.DEVCYCLE_API_CLIENT_SECRET!,\n      }),\n    });\n\n    if (!response.ok) {\n      throw new Error(`Auth failed: ${response.status}`);\n    }\n\n    const data: AuthToken = await response.json();\n\n    tokenCache = {\n      token: data.access_token,\n      expiresAt: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,\n    };\n\n    return data.access_token;\n  } catch (error) {\n    console.error(\"Failed to get auth token:\", error);\n    throw error;\n  }\n}\n","ts",[58,932,933,938,942,952,965,977,988,992,996,1041,1045,1057,1083,1091,1096,1100,1107,1132,1142,1147,1160,1165,1178,1188,1198,1211,1223,1228,1233,1237,1250,1280,1285,1289,1313,1317,1326,1331,1372,1378,1383,1391,1403,1420,1429,1434],{"__ignoreMap":273},[277,934,935],{"class":279,"line":280},[277,936,937],{"class":283},"\u002F\u002F netlify\u002Ffunctions\u002Ffeature-flags.mts\n",[277,939,940],{"class":279,"line":287},[277,941,291],{"emptyLinePlaceholder":290},[277,943,944,947,950],{"class":279,"line":294},[277,945,946],{"class":297},"interface",[277,948,949],{"class":332}," AuthToken",[277,951,875],{"class":301},[277,953,954,957,960,963],{"class":279,"line":315},[277,955,956],{"class":500},"  access_token",[277,958,959],{"class":297},":",[277,961,962],{"class":649}," string",[277,964,312],{"class":301},[277,966,967,970,972,975],{"class":279,"line":321},[277,968,969],{"class":500},"  expires_in",[277,971,959],{"class":297},[277,973,974],{"class":649}," number",[277,976,312],{"class":301},[277,978,979,982,984,986],{"class":279,"line":326},[277,980,981],{"class":500},"  token_type",[277,983,959],{"class":297},[277,985,962],{"class":649},[277,987,312],{"class":301},[277,989,990],{"class":279,"line":339},[277,991,616],{"class":301},[277,993,994],{"class":279,"line":348},[277,995,291],{"emptyLinePlaceholder":290},[277,997,998,1001,1004,1006,1009,1012,1014,1016,1019,1022,1024,1026,1029,1032,1035,1037,1039],{"class":279,"line":374},[277,999,1000],{"class":297},"let",[277,1002,1003],{"class":301}," tokenCache",[277,1005,959],{"class":297},[277,1007,1008],{"class":301}," { ",[277,1010,1011],{"class":500},"token",[277,1013,959],{"class":297},[277,1015,962],{"class":649},[277,1017,1018],{"class":301},"; ",[277,1020,1021],{"class":500},"expiresAt",[277,1023,959],{"class":297},[277,1025,974],{"class":649},[277,1027,1028],{"class":301}," } ",[277,1030,1031],{"class":297},"|",[277,1033,1034],{"class":649}," null",[277,1036,763],{"class":297},[277,1038,1034],{"class":649},[277,1040,312],{"class":301},[277,1042,1043],{"class":279,"line":385},[277,1044,291],{"emptyLinePlaceholder":290},[277,1046,1047,1050,1052,1055],{"class":279,"line":396},[277,1048,1049],{"class":297},"async",[277,1051,747],{"class":297},[277,1053,1054],{"class":332}," getAuthToken",[277,1056,336],{"class":301},[277,1058,1059,1062,1065,1068,1071,1074,1077,1080],{"class":279,"line":407},[277,1060,1061],{"class":297},"  if",[277,1063,1064],{"class":301}," (tokenCache ",[277,1066,1067],{"class":297},"&&",[277,1069,1070],{"class":301}," tokenCache.expiresAt ",[277,1072,1073],{"class":297},">",[277,1075,1076],{"class":301}," Date.",[277,1078,1079],{"class":332},"now",[277,1081,1082],{"class":301},"()) {\n",[277,1084,1085,1088],{"class":279,"line":432},[277,1086,1087],{"class":297},"    return",[277,1089,1090],{"class":301}," tokenCache.token;\n",[277,1092,1093],{"class":279,"line":453},[277,1094,1095],{"class":301},"  }\n",[277,1097,1098],{"class":279,"line":474},[277,1099,291],{"emptyLinePlaceholder":290},[277,1101,1102,1105],{"class":279,"line":495},[277,1103,1104],{"class":297},"  try",[277,1106,875],{"class":301},[277,1108,1109,1112,1115,1117,1120,1123,1126,1129],{"class":279,"line":504},[277,1110,1111],{"class":297},"    const",[277,1113,1114],{"class":649}," response",[277,1116,763],{"class":297},[277,1118,1119],{"class":297}," await",[277,1121,1122],{"class":332}," fetch",[277,1124,1125],{"class":301},"(",[277,1127,1128],{"class":308},"\"https:\u002F\u002Fauth.devcycle.com\u002Foauth\u002Ftoken\"",[277,1130,1131],{"class":301},", {\n",[277,1133,1134,1137,1140],{"class":279,"line":515},[277,1135,1136],{"class":301},"      method: ",[277,1138,1139],{"class":308},"\"POST\"",[277,1141,659],{"class":301},[277,1143,1144],{"class":279,"line":526},[277,1145,1146],{"class":301},"      headers: {\n",[277,1148,1149,1152,1155,1158],{"class":279,"line":532},[277,1150,1151],{"class":308},"        \"Content-Type\"",[277,1153,1154],{"class":301},": ",[277,1156,1157],{"class":308},"\"application\u002Fx-www-form-urlencoded\"",[277,1159,659],{"class":301},[277,1161,1162],{"class":279,"line":538},[277,1163,1164],{"class":301},"      },\n",[277,1166,1167,1170,1173,1176],{"class":279,"line":544},[277,1168,1169],{"class":301},"      body: ",[277,1171,1172],{"class":297},"new",[277,1174,1175],{"class":332}," URLSearchParams",[277,1177,636],{"class":301},[277,1179,1180,1183,1186],{"class":279,"line":550},[277,1181,1182],{"class":301},"        grant_type: ",[277,1184,1185],{"class":308},"\"client_credentials\"",[277,1187,659],{"class":301},[277,1189,1190,1193,1196],{"class":279,"line":556},[277,1191,1192],{"class":301},"        audience: ",[277,1194,1195],{"class":308},"\"https:\u002F\u002Fapi.devcycle.com\u002F\"",[277,1197,659],{"class":301},[277,1199,1200,1203,1206,1209],{"class":279,"line":566},[277,1201,1202],{"class":301},"        client_id: process.env.",[277,1204,1205],{"class":649},"DEVCYCLE_API_CLIENT_ID",[277,1207,1208],{"class":297},"!",[277,1210,659],{"class":301},[277,1212,1213,1216,1219,1221],{"class":279,"line":576},[277,1214,1215],{"class":301},"        client_secret: process.env.",[277,1217,1218],{"class":649},"DEVCYCLE_API_CLIENT_SECRET",[277,1220,1208],{"class":297},[277,1222,659],{"class":301},[277,1224,1225],{"class":279,"line":586},[277,1226,1227],{"class":301},"      }),\n",[277,1229,1230],{"class":279,"line":596},[277,1231,1232],{"class":301},"    });\n",[277,1234,1235],{"class":279,"line":607},[277,1236,291],{"emptyLinePlaceholder":290},[277,1238,1239,1242,1245,1247],{"class":279,"line":613},[277,1240,1241],{"class":297},"    if",[277,1243,1244],{"class":301}," (",[277,1246,1208],{"class":297},[277,1248,1249],{"class":301},"response.ok) {\n",[277,1251,1252,1255,1258,1261,1263,1266,1269,1271,1274,1277],{"class":279,"line":619},[277,1253,1254],{"class":297},"      throw",[277,1256,1257],{"class":297}," new",[277,1259,1260],{"class":332}," Error",[277,1262,1125],{"class":301},[277,1264,1265],{"class":308},"`Auth failed: ${",[277,1267,1268],{"class":301},"response",[277,1270,182],{"class":308},[277,1272,1273],{"class":301},"status",[277,1275,1276],{"class":308},"}`",[277,1278,1279],{"class":301},");\n",[277,1281,1282],{"class":279,"line":624},[277,1283,1284],{"class":301},"    }\n",[277,1286,1287],{"class":279,"line":639},[277,1288,291],{"emptyLinePlaceholder":290},[277,1290,1291,1293,1296,1298,1300,1302,1304,1307,1310],{"class":279,"line":662},[277,1292,1111],{"class":297},[277,1294,1295],{"class":649}," data",[277,1297,959],{"class":297},[277,1299,949],{"class":332},[277,1301,763],{"class":297},[277,1303,1119],{"class":297},[277,1305,1306],{"class":301}," response.",[277,1308,1309],{"class":332},"json",[277,1311,1312],{"class":301},"();\n",[277,1314,1315],{"class":279,"line":668},[277,1316,291],{"emptyLinePlaceholder":290},[277,1318,1319,1322,1324],{"class":279,"line":679},[277,1320,1321],{"class":301},"    tokenCache ",[277,1323,357],{"class":297},[277,1325,875],{"class":301},[277,1327,1328],{"class":279,"line":685},[277,1329,1330],{"class":301},"      token: data.access_token,\n",[277,1332,1334,1337,1339,1342,1345,1348,1351,1354,1357,1360,1363,1366,1368,1370],{"class":279,"line":1333},38,[277,1335,1336],{"class":301},"      expiresAt: Date.",[277,1338,1079],{"class":332},[277,1340,1341],{"class":301},"() ",[277,1343,1344],{"class":297},"+",[277,1346,1347],{"class":301}," data.expires_in ",[277,1349,1350],{"class":297},"*",[277,1352,1353],{"class":649}," 1000",[277,1355,1356],{"class":297}," -",[277,1358,1359],{"class":649}," 5",[277,1361,1362],{"class":297}," *",[277,1364,1365],{"class":649}," 60",[277,1367,1362],{"class":297},[277,1369,1353],{"class":649},[277,1371,659],{"class":301},[277,1373,1375],{"class":279,"line":1374},39,[277,1376,1377],{"class":301},"    };\n",[277,1379,1381],{"class":279,"line":1380},40,[277,1382,291],{"emptyLinePlaceholder":290},[277,1384,1386,1388],{"class":279,"line":1385},41,[277,1387,1087],{"class":297},[277,1389,1390],{"class":301}," data.access_token;\n",[277,1392,1394,1397,1400],{"class":279,"line":1393},42,[277,1395,1396],{"class":301},"  } ",[277,1398,1399],{"class":297},"catch",[277,1401,1402],{"class":301}," (error) {\n",[277,1404,1406,1409,1412,1414,1417],{"class":279,"line":1405},43,[277,1407,1408],{"class":301},"    console.",[277,1410,1411],{"class":332},"error",[277,1413,1125],{"class":301},[277,1415,1416],{"class":308},"\"Failed to get auth token:\"",[277,1418,1419],{"class":301},", error);\n",[277,1421,1423,1426],{"class":279,"line":1422},44,[277,1424,1425],{"class":297},"    throw",[277,1427,1428],{"class":301}," error;\n",[277,1430,1432],{"class":279,"line":1431},45,[277,1433,1095],{"class":301},[277,1435,1437],{"class":279,"line":1436},46,[277,1438,616],{"class":301},[10,1440,1441],{},"Now we can fetch the available feature flags, and their config (to get the currently active variation) using the below code:",[268,1443,1445],{"className":270,"code":1444,"language":272,"meta":273,"style":273},"\u002F\u002F netlify\u002Ffunctions\u002Ffeature-flags.mts\n\ninterface Distribution {\n  _variation: string;\n  percentage: number;\n}\n\ninterface FeatureConfig {\n  _feature: string;\n  _environment: string;\n  status: string;\n  targets: {\n    _id: string;\n    name: string;\n    distribution: Distribution[];\n    audience: {\n      name: string;\n      filters: {\n        operator: \"and\" | \"or\";\n        filters: { type: string }[];\n      };\n    };\n  }[];\n}\n\nasync function getFeatures(\n  featuresUrl: string,\n  headers: Record\u003Cstring, string>,\n) {\n  const featuresResponse = await fetch(featuresUrl, { headers });\n\n  if (!featuresResponse.ok) {\n    throw new Error(`Failed to fetch flags: ${featuresResponse.status}`);\n  }\n\n  const featuresData = await featuresResponse.json();\n\n  \u002F\u002F Fetch the config for each of the feature flags\n  \u002F\u002F We're only fetching the configs for the development env\n  const configPromises = featuresData.map(async (feature: any) => {\n    const configResponse = await fetch(\n      `${featuresUrl}\u002F${feature._id}\u002Fconfigurations?environment=development`,\n      { headers },\n    );\n\n    if (!configResponse.ok) {\n      console.error(`Failed to fetch config for feature ${feature._id}`);\n      return null;\n    }\n\n    const configs: FeatureConfig[] = await configResponse.json();\n\n    return {\n      ...feature,\n      targets: configs[0].targets,\n      status: configs[0].status,\n    };\n  });\n\n  const featuresWithConfigs = await Promise.all(configPromises);\n  return featuresWithConfigs.filter((f) => f !== null);\n}\n",[58,1446,1447,1451,1455,1464,1475,1486,1490,1494,1503,1514,1525,1536,1545,1556,1567,1579,1588,1599,1608,1626,1645,1650,1654,1659,1663,1667,1678,1689,1713,1718,1734,1738,1749,1773,1777,1781,1799,1803,1808,1813,1850,1865,1888,1893,1898,1902,1913,1936,1946,1951,1956,1982,1987,1994,2003,2015,2026,2031,2037,2042,2065,2096],{"__ignoreMap":273},[277,1448,1449],{"class":279,"line":280},[277,1450,937],{"class":283},[277,1452,1453],{"class":279,"line":287},[277,1454,291],{"emptyLinePlaceholder":290},[277,1456,1457,1459,1462],{"class":279,"line":294},[277,1458,946],{"class":297},[277,1460,1461],{"class":332}," Distribution",[277,1463,875],{"class":301},[277,1465,1466,1469,1471,1473],{"class":279,"line":315},[277,1467,1468],{"class":500},"  _variation",[277,1470,959],{"class":297},[277,1472,962],{"class":649},[277,1474,312],{"class":301},[277,1476,1477,1480,1482,1484],{"class":279,"line":321},[277,1478,1479],{"class":500},"  percentage",[277,1481,959],{"class":297},[277,1483,974],{"class":649},[277,1485,312],{"class":301},[277,1487,1488],{"class":279,"line":326},[277,1489,616],{"class":301},[277,1491,1492],{"class":279,"line":339},[277,1493,291],{"emptyLinePlaceholder":290},[277,1495,1496,1498,1501],{"class":279,"line":348},[277,1497,946],{"class":297},[277,1499,1500],{"class":332}," FeatureConfig",[277,1502,875],{"class":301},[277,1504,1505,1508,1510,1512],{"class":279,"line":374},[277,1506,1507],{"class":500},"  _feature",[277,1509,959],{"class":297},[277,1511,962],{"class":649},[277,1513,312],{"class":301},[277,1515,1516,1519,1521,1523],{"class":279,"line":385},[277,1517,1518],{"class":500},"  _environment",[277,1520,959],{"class":297},[277,1522,962],{"class":649},[277,1524,312],{"class":301},[277,1526,1527,1530,1532,1534],{"class":279,"line":396},[277,1528,1529],{"class":500},"  status",[277,1531,959],{"class":297},[277,1533,962],{"class":649},[277,1535,312],{"class":301},[277,1537,1538,1541,1543],{"class":279,"line":407},[277,1539,1540],{"class":500},"  targets",[277,1542,959],{"class":297},[277,1544,875],{"class":301},[277,1546,1547,1550,1552,1554],{"class":279,"line":432},[277,1548,1549],{"class":500},"    _id",[277,1551,959],{"class":297},[277,1553,962],{"class":649},[277,1555,312],{"class":301},[277,1557,1558,1561,1563,1565],{"class":279,"line":453},[277,1559,1560],{"class":500},"    name",[277,1562,959],{"class":297},[277,1564,962],{"class":649},[277,1566,312],{"class":301},[277,1568,1569,1572,1574,1576],{"class":279,"line":474},[277,1570,1571],{"class":500},"    distribution",[277,1573,959],{"class":297},[277,1575,1461],{"class":332},[277,1577,1578],{"class":301},"[];\n",[277,1580,1581,1584,1586],{"class":279,"line":495},[277,1582,1583],{"class":500},"    audience",[277,1585,959],{"class":297},[277,1587,875],{"class":301},[277,1589,1590,1593,1595,1597],{"class":279,"line":504},[277,1591,1592],{"class":500},"      name",[277,1594,959],{"class":297},[277,1596,962],{"class":649},[277,1598,312],{"class":301},[277,1600,1601,1604,1606],{"class":279,"line":515},[277,1602,1603],{"class":500},"      filters",[277,1605,959],{"class":297},[277,1607,875],{"class":301},[277,1609,1610,1613,1615,1618,1621,1624],{"class":279,"line":526},[277,1611,1612],{"class":500},"        operator",[277,1614,959],{"class":297},[277,1616,1617],{"class":308}," \"and\"",[277,1619,1620],{"class":297}," |",[277,1622,1623],{"class":308}," \"or\"",[277,1625,312],{"class":301},[277,1627,1628,1631,1633,1635,1638,1640,1642],{"class":279,"line":532},[277,1629,1630],{"class":500},"        filters",[277,1632,959],{"class":297},[277,1634,1008],{"class":301},[277,1636,1637],{"class":500},"type",[277,1639,959],{"class":297},[277,1641,962],{"class":649},[277,1643,1644],{"class":301}," }[];\n",[277,1646,1647],{"class":279,"line":538},[277,1648,1649],{"class":301},"      };\n",[277,1651,1652],{"class":279,"line":544},[277,1653,1377],{"class":301},[277,1655,1656],{"class":279,"line":550},[277,1657,1658],{"class":301},"  }[];\n",[277,1660,1661],{"class":279,"line":556},[277,1662,616],{"class":301},[277,1664,1665],{"class":279,"line":566},[277,1666,291],{"emptyLinePlaceholder":290},[277,1668,1669,1671,1673,1676],{"class":279,"line":576},[277,1670,1049],{"class":297},[277,1672,747],{"class":297},[277,1674,1675],{"class":332}," getFeatures",[277,1677,769],{"class":301},[277,1679,1680,1683,1685,1687],{"class":279,"line":586},[277,1681,1682],{"class":500},"  featuresUrl",[277,1684,959],{"class":297},[277,1686,962],{"class":649},[277,1688,659],{"class":301},[277,1690,1691,1694,1696,1699,1702,1705,1708,1710],{"class":279,"line":596},[277,1692,1693],{"class":500},"  headers",[277,1695,959],{"class":297},[277,1697,1698],{"class":332}," Record",[277,1700,1701],{"class":301},"\u003C",[277,1703,1704],{"class":649},"string",[277,1706,1707],{"class":301},", ",[277,1709,1704],{"class":649},[277,1711,1712],{"class":301},">,\n",[277,1714,1715],{"class":279,"line":607},[277,1716,1717],{"class":301},") {\n",[277,1719,1720,1722,1725,1727,1729,1731],{"class":279,"line":613},[277,1721,757],{"class":297},[277,1723,1724],{"class":649}," featuresResponse",[277,1726,763],{"class":297},[277,1728,1119],{"class":297},[277,1730,1122],{"class":332},[277,1732,1733],{"class":301},"(featuresUrl, { headers });\n",[277,1735,1736],{"class":279,"line":619},[277,1737,291],{"emptyLinePlaceholder":290},[277,1739,1740,1742,1744,1746],{"class":279,"line":624},[277,1741,1061],{"class":297},[277,1743,1244],{"class":301},[277,1745,1208],{"class":297},[277,1747,1748],{"class":301},"featuresResponse.ok) {\n",[277,1750,1751,1753,1755,1757,1759,1762,1765,1767,1769,1771],{"class":279,"line":639},[277,1752,1425],{"class":297},[277,1754,1257],{"class":297},[277,1756,1260],{"class":332},[277,1758,1125],{"class":301},[277,1760,1761],{"class":308},"`Failed to fetch flags: ${",[277,1763,1764],{"class":301},"featuresResponse",[277,1766,182],{"class":308},[277,1768,1273],{"class":301},[277,1770,1276],{"class":308},[277,1772,1279],{"class":301},[277,1774,1775],{"class":279,"line":662},[277,1776,1095],{"class":301},[277,1778,1779],{"class":279,"line":668},[277,1780,291],{"emptyLinePlaceholder":290},[277,1782,1783,1785,1788,1790,1792,1795,1797],{"class":279,"line":679},[277,1784,757],{"class":297},[277,1786,1787],{"class":649}," featuresData",[277,1789,763],{"class":297},[277,1791,1119],{"class":297},[277,1793,1794],{"class":301}," featuresResponse.",[277,1796,1309],{"class":332},[277,1798,1312],{"class":301},[277,1800,1801],{"class":279,"line":685},[277,1802,291],{"emptyLinePlaceholder":290},[277,1804,1805],{"class":279,"line":1333},[277,1806,1807],{"class":283},"  \u002F\u002F Fetch the config for each of the feature flags\n",[277,1809,1810],{"class":279,"line":1374},[277,1811,1812],{"class":283},"  \u002F\u002F We're only fetching the configs for the development env\n",[277,1814,1815,1817,1820,1822,1825,1828,1830,1832,1834,1837,1839,1842,1845,1848],{"class":279,"line":1380},[277,1816,757],{"class":297},[277,1818,1819],{"class":649}," configPromises",[277,1821,763],{"class":297},[277,1823,1824],{"class":301}," featuresData.",[277,1826,1827],{"class":332},"map",[277,1829,1125],{"class":301},[277,1831,1049],{"class":297},[277,1833,1244],{"class":301},[277,1835,1836],{"class":500},"feature",[277,1838,959],{"class":297},[277,1840,1841],{"class":649}," any",[277,1843,1844],{"class":301},") ",[277,1846,1847],{"class":297},"=>",[277,1849,875],{"class":301},[277,1851,1852,1854,1857,1859,1861,1863],{"class":279,"line":1385},[277,1853,1111],{"class":297},[277,1855,1856],{"class":649}," configResponse",[277,1858,763],{"class":297},[277,1860,1119],{"class":297},[277,1862,1122],{"class":332},[277,1864,769],{"class":301},[277,1866,1867,1870,1873,1876,1878,1880,1883,1886],{"class":279,"line":1393},[277,1868,1869],{"class":308},"      `${",[277,1871,1872],{"class":301},"featuresUrl",[277,1874,1875],{"class":308},"}\u002F${",[277,1877,1836],{"class":301},[277,1879,182],{"class":308},[277,1881,1882],{"class":301},"_id",[277,1884,1885],{"class":308},"}\u002Fconfigurations?environment=development`",[277,1887,659],{"class":301},[277,1889,1890],{"class":279,"line":1405},[277,1891,1892],{"class":301},"      { headers },\n",[277,1894,1895],{"class":279,"line":1422},[277,1896,1897],{"class":301},"    );\n",[277,1899,1900],{"class":279,"line":1431},[277,1901,291],{"emptyLinePlaceholder":290},[277,1903,1904,1906,1908,1910],{"class":279,"line":1436},[277,1905,1241],{"class":297},[277,1907,1244],{"class":301},[277,1909,1208],{"class":297},[277,1911,1912],{"class":301},"configResponse.ok) {\n",[277,1914,1916,1919,1921,1923,1926,1928,1930,1932,1934],{"class":279,"line":1915},47,[277,1917,1918],{"class":301},"      console.",[277,1920,1411],{"class":332},[277,1922,1125],{"class":301},[277,1924,1925],{"class":308},"`Failed to fetch config for feature ${",[277,1927,1836],{"class":301},[277,1929,182],{"class":308},[277,1931,1882],{"class":301},[277,1933,1276],{"class":308},[277,1935,1279],{"class":301},[277,1937,1939,1942,1944],{"class":279,"line":1938},48,[277,1940,1941],{"class":297},"      return",[277,1943,1034],{"class":649},[277,1945,312],{"class":301},[277,1947,1949],{"class":279,"line":1948},49,[277,1950,1284],{"class":301},[277,1952,1954],{"class":279,"line":1953},50,[277,1955,291],{"emptyLinePlaceholder":290},[277,1957,1959,1961,1964,1966,1968,1971,1973,1975,1978,1980],{"class":279,"line":1958},51,[277,1960,1111],{"class":297},[277,1962,1963],{"class":649}," configs",[277,1965,959],{"class":297},[277,1967,1500],{"class":332},[277,1969,1970],{"class":301},"[] ",[277,1972,357],{"class":297},[277,1974,1119],{"class":297},[277,1976,1977],{"class":301}," configResponse.",[277,1979,1309],{"class":332},[277,1981,1312],{"class":301},[277,1983,1985],{"class":279,"line":1984},52,[277,1986,291],{"emptyLinePlaceholder":290},[277,1988,1990,1992],{"class":279,"line":1989},53,[277,1991,1087],{"class":297},[277,1993,875],{"class":301},[277,1995,1997,2000],{"class":279,"line":1996},54,[277,1998,1999],{"class":297},"      ...",[277,2001,2002],{"class":301},"feature,\n",[277,2004,2006,2009,2012],{"class":279,"line":2005},55,[277,2007,2008],{"class":301},"      targets: configs[",[277,2010,2011],{"class":649},"0",[277,2013,2014],{"class":301},"].targets,\n",[277,2016,2018,2021,2023],{"class":279,"line":2017},56,[277,2019,2020],{"class":301},"      status: configs[",[277,2022,2011],{"class":649},[277,2024,2025],{"class":301},"].status,\n",[277,2027,2029],{"class":279,"line":2028},57,[277,2030,1377],{"class":301},[277,2032,2034],{"class":279,"line":2033},58,[277,2035,2036],{"class":301},"  });\n",[277,2038,2040],{"class":279,"line":2039},59,[277,2041,291],{"emptyLinePlaceholder":290},[277,2043,2045,2047,2050,2052,2054,2057,2059,2062],{"class":279,"line":2044},60,[277,2046,757],{"class":297},[277,2048,2049],{"class":649}," featuresWithConfigs",[277,2051,763],{"class":297},[277,2053,1119],{"class":297},[277,2055,2056],{"class":649}," Promise",[277,2058,182],{"class":301},[277,2060,2061],{"class":332},"all",[277,2063,2064],{"class":301},"(configPromises);\n",[277,2066,2068,2070,2073,2076,2079,2082,2084,2086,2089,2092,2094],{"class":279,"line":2067},61,[277,2069,342],{"class":297},[277,2071,2072],{"class":301}," featuresWithConfigs.",[277,2074,2075],{"class":332},"filter",[277,2077,2078],{"class":301},"((",[277,2080,2081],{"class":500},"f",[277,2083,1844],{"class":301},[277,2085,1847],{"class":297},[277,2087,2088],{"class":301}," f ",[277,2090,2091],{"class":297},"!==",[277,2093,1034],{"class":649},[277,2095,1279],{"class":301},[277,2097,2099],{"class":279,"line":2098},62,[277,2100,616],{"class":301},[10,2102,2103],{},"To update the feature flags config (changing the variation, or toggle the flag altogether), we can use the below function:",[268,2105,2107],{"className":270,"code":2106,"language":272,"meta":273,"style":273},"async function updateFeature(\n  featuresUrl: string,\n  featureId: string,\n  headers: Record\u003Cstring, string>,\n  update: any,\n) {\n  const response = await fetch(\n    `${featuresUrl}\u002F${featureId}\u002Fconfigurations?environment=development`,\n    {\n      method: \"PATCH\",\n      headers,\n      body: JSON.stringify(update),\n    },\n  );\n\n  if (!response.ok) {\n    throw new Error(`Failed to update feature: ${response.status}`);\n  }\n\n  return await response.json();\n}\n",[58,2108,2109,2120,2130,2141,2159,2170,2174,2188,2204,2209,2218,2223,2238,2243,2247,2251,2261,2284,2288,2292,2304],{"__ignoreMap":273},[277,2110,2111,2113,2115,2118],{"class":279,"line":280},[277,2112,1049],{"class":297},[277,2114,747],{"class":297},[277,2116,2117],{"class":332}," updateFeature",[277,2119,769],{"class":301},[277,2121,2122,2124,2126,2128],{"class":279,"line":287},[277,2123,1682],{"class":500},[277,2125,959],{"class":297},[277,2127,962],{"class":649},[277,2129,659],{"class":301},[277,2131,2132,2135,2137,2139],{"class":279,"line":294},[277,2133,2134],{"class":500},"  featureId",[277,2136,959],{"class":297},[277,2138,962],{"class":649},[277,2140,659],{"class":301},[277,2142,2143,2145,2147,2149,2151,2153,2155,2157],{"class":279,"line":315},[277,2144,1693],{"class":500},[277,2146,959],{"class":297},[277,2148,1698],{"class":332},[277,2150,1701],{"class":301},[277,2152,1704],{"class":649},[277,2154,1707],{"class":301},[277,2156,1704],{"class":649},[277,2158,1712],{"class":301},[277,2160,2161,2164,2166,2168],{"class":279,"line":321},[277,2162,2163],{"class":500},"  update",[277,2165,959],{"class":297},[277,2167,1841],{"class":649},[277,2169,659],{"class":301},[277,2171,2172],{"class":279,"line":326},[277,2173,1717],{"class":301},[277,2175,2176,2178,2180,2182,2184,2186],{"class":279,"line":339},[277,2177,757],{"class":297},[277,2179,1114],{"class":649},[277,2181,763],{"class":297},[277,2183,1119],{"class":297},[277,2185,1122],{"class":332},[277,2187,769],{"class":301},[277,2189,2190,2193,2195,2197,2200,2202],{"class":279,"line":348},[277,2191,2192],{"class":308},"    `${",[277,2194,1872],{"class":301},[277,2196,1875],{"class":308},[277,2198,2199],{"class":301},"featureId",[277,2201,1885],{"class":308},[277,2203,659],{"class":301},[277,2205,2206],{"class":279,"line":374},[277,2207,2208],{"class":301},"    {\n",[277,2210,2211,2213,2216],{"class":279,"line":385},[277,2212,1136],{"class":301},[277,2214,2215],{"class":308},"\"PATCH\"",[277,2217,659],{"class":301},[277,2219,2220],{"class":279,"line":396},[277,2221,2222],{"class":301},"      headers,\n",[277,2224,2225,2227,2230,2232,2235],{"class":279,"line":407},[277,2226,1169],{"class":301},[277,2228,2229],{"class":649},"JSON",[277,2231,182],{"class":301},[277,2233,2234],{"class":332},"stringify",[277,2236,2237],{"class":301},"(update),\n",[277,2239,2240],{"class":279,"line":432},[277,2241,2242],{"class":301},"    },\n",[277,2244,2245],{"class":279,"line":453},[277,2246,610],{"class":301},[277,2248,2249],{"class":279,"line":474},[277,2250,291],{"emptyLinePlaceholder":290},[277,2252,2253,2255,2257,2259],{"class":279,"line":495},[277,2254,1061],{"class":297},[277,2256,1244],{"class":301},[277,2258,1208],{"class":297},[277,2260,1249],{"class":301},[277,2262,2263,2265,2267,2269,2271,2274,2276,2278,2280,2282],{"class":279,"line":504},[277,2264,1425],{"class":297},[277,2266,1257],{"class":297},[277,2268,1260],{"class":332},[277,2270,1125],{"class":301},[277,2272,2273],{"class":308},"`Failed to update feature: ${",[277,2275,1268],{"class":301},[277,2277,182],{"class":308},[277,2279,1273],{"class":301},[277,2281,1276],{"class":308},[277,2283,1279],{"class":301},[277,2285,2286],{"class":279,"line":515},[277,2287,1095],{"class":301},[277,2289,2290],{"class":279,"line":526},[277,2291,291],{"emptyLinePlaceholder":290},[277,2293,2294,2296,2298,2300,2302],{"class":279,"line":532},[277,2295,342],{"class":297},[277,2297,1119],{"class":297},[277,2299,1306],{"class":301},[277,2301,1309],{"class":332},[277,2303,1312],{"class":301},[277,2305,2306],{"class":279,"line":538},[277,2307,616],{"class":301},[10,2309,2310],{},"Finally, here is the Netlify function that uses the above functions to serve the client requests:",[268,2312,2314],{"className":270,"code":2313,"language":272,"meta":273,"style":273},"export default async (req: Request) => {\n  try {\n    const { method } = req;\n    const token = await getAuthToken();\n    const featuresBaseUrl = `https:\u002F\u002Fapi.devcycle.com\u002Fv1\u002Fprojects\u002F${process.env.DEVCYCLE_PROJECT_ID}\u002Ffeatures`;\n    const headers = {\n      Authorization: `Bearer ${token}`,\n    };\n\n    if (method === \"GET\") {\n      const features = await getFeatures(featuresBaseUrl, headers);\n\n      return Response.json({ features });\n    }\n\n    if (method === \"PATCH\") {\n      const body = await req.json();\n      const { featureId, update } = body;\n\n      const data = await updateFeature(\n        featuresBaseUrl,\n        featureId,\n        {\n          ...headers,\n          \"Content-Type\": \"application\u002Fjson\",\n        },\n        update,\n      );\n\n      return Response.json({ data });\n    }\n\n    return new Response(JSON.stringify({ error: \"Method not allowed\" }), {\n      status: 405,\n      headers: {\n        \"Content-Type\": \"application\u002Fjson\",\n      },\n    });\n  } catch (error) {\n    console.error(\"Error:\", error);\n    return new Response(\n      JSON.stringify({\n        error: error instanceof Error ? error.message : \"Internal server error\",\n      }),\n      {\n        status: 500,\n        headers: {\n          \"Content-Type\": \"application\u002Fjson\",\n        },\n      },\n    );\n  }\n};\n",[58,2315,2316,2341,2347,2363,2378,2408,2419,2433,2437,2441,2456,2473,2477,2489,2493,2497,2510,2528,2548,2552,2566,2571,2576,2581,2589,2601,2606,2611,2616,2620,2631,2635,2639,2665,2675,2679,2689,2693,2697,2705,2718,2728,2739,2762,2766,2771,2781,2786,2796,2800,2804,2808,2812],{"__ignoreMap":273},[277,2317,2318,2320,2322,2325,2327,2330,2332,2335,2337,2339],{"class":279,"line":280},[277,2319,627],{"class":297},[277,2321,630],{"class":297},[277,2323,2324],{"class":297}," async",[277,2326,1244],{"class":301},[277,2328,2329],{"class":500},"req",[277,2331,959],{"class":297},[277,2333,2334],{"class":332}," Request",[277,2336,1844],{"class":301},[277,2338,1847],{"class":297},[277,2340,875],{"class":301},[277,2342,2343,2345],{"class":279,"line":287},[277,2344,1104],{"class":297},[277,2346,875],{"class":301},[277,2348,2349,2351,2353,2356,2358,2360],{"class":279,"line":294},[277,2350,1111],{"class":297},[277,2352,1008],{"class":301},[277,2354,2355],{"class":649},"method",[277,2357,1028],{"class":301},[277,2359,357],{"class":297},[277,2361,2362],{"class":301}," req;\n",[277,2364,2365,2367,2370,2372,2374,2376],{"class":279,"line":315},[277,2366,1111],{"class":297},[277,2368,2369],{"class":649}," token",[277,2371,763],{"class":297},[277,2373,1119],{"class":297},[277,2375,1054],{"class":332},[277,2377,1312],{"class":301},[277,2379,2380,2382,2385,2387,2390,2393,2395,2398,2400,2403,2406],{"class":279,"line":321},[277,2381,1111],{"class":297},[277,2383,2384],{"class":649}," featuresBaseUrl",[277,2386,763],{"class":297},[277,2388,2389],{"class":308}," `https:\u002F\u002Fapi.devcycle.com\u002Fv1\u002Fprojects\u002F${",[277,2391,2392],{"class":301},"process",[277,2394,182],{"class":308},[277,2396,2397],{"class":301},"env",[277,2399,182],{"class":308},[277,2401,2402],{"class":649},"DEVCYCLE_PROJECT_ID",[277,2404,2405],{"class":308},"}\u002Ffeatures`",[277,2407,312],{"class":301},[277,2409,2410,2412,2415,2417],{"class":279,"line":326},[277,2411,1111],{"class":297},[277,2413,2414],{"class":649}," headers",[277,2416,763],{"class":297},[277,2418,875],{"class":301},[277,2420,2421,2424,2427,2429,2431],{"class":279,"line":339},[277,2422,2423],{"class":301},"      Authorization: ",[277,2425,2426],{"class":308},"`Bearer ${",[277,2428,1011],{"class":301},[277,2430,1276],{"class":308},[277,2432,659],{"class":301},[277,2434,2435],{"class":279,"line":348},[277,2436,1377],{"class":301},[277,2438,2439],{"class":279,"line":374},[277,2440,291],{"emptyLinePlaceholder":290},[277,2442,2443,2445,2448,2451,2454],{"class":279,"line":385},[277,2444,1241],{"class":297},[277,2446,2447],{"class":301}," (method ",[277,2449,2450],{"class":297},"===",[277,2452,2453],{"class":308}," \"GET\"",[277,2455,1717],{"class":301},[277,2457,2458,2461,2464,2466,2468,2470],{"class":279,"line":396},[277,2459,2460],{"class":297},"      const",[277,2462,2463],{"class":649}," features",[277,2465,763],{"class":297},[277,2467,1119],{"class":297},[277,2469,1675],{"class":332},[277,2471,2472],{"class":301},"(featuresBaseUrl, headers);\n",[277,2474,2475],{"class":279,"line":407},[277,2476,291],{"emptyLinePlaceholder":290},[277,2478,2479,2481,2484,2486],{"class":279,"line":432},[277,2480,1941],{"class":297},[277,2482,2483],{"class":301}," Response.",[277,2485,1309],{"class":332},[277,2487,2488],{"class":301},"({ features });\n",[277,2490,2491],{"class":279,"line":453},[277,2492,1284],{"class":301},[277,2494,2495],{"class":279,"line":474},[277,2496,291],{"emptyLinePlaceholder":290},[277,2498,2499,2501,2503,2505,2508],{"class":279,"line":495},[277,2500,1241],{"class":297},[277,2502,2447],{"class":301},[277,2504,2450],{"class":297},[277,2506,2507],{"class":308}," \"PATCH\"",[277,2509,1717],{"class":301},[277,2511,2512,2514,2517,2519,2521,2524,2526],{"class":279,"line":504},[277,2513,2460],{"class":297},[277,2515,2516],{"class":649}," body",[277,2518,763],{"class":297},[277,2520,1119],{"class":297},[277,2522,2523],{"class":301}," req.",[277,2525,1309],{"class":332},[277,2527,1312],{"class":301},[277,2529,2530,2532,2534,2536,2538,2541,2543,2545],{"class":279,"line":515},[277,2531,2460],{"class":297},[277,2533,1008],{"class":301},[277,2535,2199],{"class":649},[277,2537,1707],{"class":301},[277,2539,2540],{"class":649},"update",[277,2542,1028],{"class":301},[277,2544,357],{"class":297},[277,2546,2547],{"class":301}," body;\n",[277,2549,2550],{"class":279,"line":526},[277,2551,291],{"emptyLinePlaceholder":290},[277,2553,2554,2556,2558,2560,2562,2564],{"class":279,"line":532},[277,2555,2460],{"class":297},[277,2557,1295],{"class":649},[277,2559,763],{"class":297},[277,2561,1119],{"class":297},[277,2563,2117],{"class":332},[277,2565,769],{"class":301},[277,2567,2568],{"class":279,"line":538},[277,2569,2570],{"class":301},"        featuresBaseUrl,\n",[277,2572,2573],{"class":279,"line":544},[277,2574,2575],{"class":301},"        featureId,\n",[277,2577,2578],{"class":279,"line":550},[277,2579,2580],{"class":301},"        {\n",[277,2582,2583,2586],{"class":279,"line":556},[277,2584,2585],{"class":297},"          ...",[277,2587,2588],{"class":301},"headers,\n",[277,2590,2591,2594,2596,2599],{"class":279,"line":566},[277,2592,2593],{"class":308},"          \"Content-Type\"",[277,2595,1154],{"class":301},[277,2597,2598],{"class":308},"\"application\u002Fjson\"",[277,2600,659],{"class":301},[277,2602,2603],{"class":279,"line":576},[277,2604,2605],{"class":301},"        },\n",[277,2607,2608],{"class":279,"line":586},[277,2609,2610],{"class":301},"        update,\n",[277,2612,2613],{"class":279,"line":596},[277,2614,2615],{"class":301},"      );\n",[277,2617,2618],{"class":279,"line":607},[277,2619,291],{"emptyLinePlaceholder":290},[277,2621,2622,2624,2626,2628],{"class":279,"line":613},[277,2623,1941],{"class":297},[277,2625,2483],{"class":301},[277,2627,1309],{"class":332},[277,2629,2630],{"class":301},"({ data });\n",[277,2632,2633],{"class":279,"line":619},[277,2634,1284],{"class":301},[277,2636,2637],{"class":279,"line":624},[277,2638,291],{"emptyLinePlaceholder":290},[277,2640,2641,2643,2645,2648,2650,2652,2654,2656,2659,2662],{"class":279,"line":639},[277,2642,1087],{"class":297},[277,2644,1257],{"class":297},[277,2646,2647],{"class":332}," Response",[277,2649,1125],{"class":301},[277,2651,2229],{"class":649},[277,2653,182],{"class":301},[277,2655,2234],{"class":332},[277,2657,2658],{"class":301},"({ error: ",[277,2660,2661],{"class":308},"\"Method not allowed\"",[277,2663,2664],{"class":301}," }), {\n",[277,2666,2667,2670,2673],{"class":279,"line":662},[277,2668,2669],{"class":301},"      status: ",[277,2671,2672],{"class":649},"405",[277,2674,659],{"class":301},[277,2676,2677],{"class":279,"line":668},[277,2678,1146],{"class":301},[277,2680,2681,2683,2685,2687],{"class":279,"line":679},[277,2682,1151],{"class":308},[277,2684,1154],{"class":301},[277,2686,2598],{"class":308},[277,2688,659],{"class":301},[277,2690,2691],{"class":279,"line":685},[277,2692,1164],{"class":301},[277,2694,2695],{"class":279,"line":1333},[277,2696,1232],{"class":301},[277,2698,2699,2701,2703],{"class":279,"line":1374},[277,2700,1396],{"class":301},[277,2702,1399],{"class":297},[277,2704,1402],{"class":301},[277,2706,2707,2709,2711,2713,2716],{"class":279,"line":1380},[277,2708,1408],{"class":301},[277,2710,1411],{"class":332},[277,2712,1125],{"class":301},[277,2714,2715],{"class":308},"\"Error:\"",[277,2717,1419],{"class":301},[277,2719,2720,2722,2724,2726],{"class":279,"line":1385},[277,2721,1087],{"class":297},[277,2723,1257],{"class":297},[277,2725,2647],{"class":332},[277,2727,769],{"class":301},[277,2729,2730,2733,2735,2737],{"class":279,"line":1393},[277,2731,2732],{"class":649},"      JSON",[277,2734,182],{"class":301},[277,2736,2234],{"class":332},[277,2738,636],{"class":301},[277,2740,2741,2744,2747,2749,2752,2755,2757,2760],{"class":279,"line":1405},[277,2742,2743],{"class":301},"        error: error ",[277,2745,2746],{"class":297},"instanceof",[277,2748,1260],{"class":332},[277,2750,2751],{"class":297}," ?",[277,2753,2754],{"class":301}," error.message ",[277,2756,959],{"class":297},[277,2758,2759],{"class":308}," \"Internal server error\"",[277,2761,659],{"class":301},[277,2763,2764],{"class":279,"line":1422},[277,2765,1227],{"class":301},[277,2767,2768],{"class":279,"line":1431},[277,2769,2770],{"class":301},"      {\n",[277,2772,2773,2776,2779],{"class":279,"line":1436},[277,2774,2775],{"class":301},"        status: ",[277,2777,2778],{"class":649},"500",[277,2780,659],{"class":301},[277,2782,2783],{"class":279,"line":1915},[277,2784,2785],{"class":301},"        headers: {\n",[277,2787,2788,2790,2792,2794],{"class":279,"line":1938},[277,2789,2593],{"class":308},[277,2791,1154],{"class":301},[277,2793,2598],{"class":308},[277,2795,659],{"class":301},[277,2797,2798],{"class":279,"line":1948},[277,2799,2605],{"class":301},[277,2801,2802],{"class":279,"line":1953},[277,2803,1164],{"class":301},[277,2805,2806],{"class":279,"line":1958},[277,2807,1897],{"class":301},[277,2809,2810],{"class":279,"line":1984},[277,2811,1095],{"class":301},[277,2813,2814],{"class":279,"line":1989},[277,2815,2816],{"class":301},"};\n",[10,2818,2819],{},"This Netlify function is called by the client in the following way:",[268,2821,2823],{"className":270,"code":2822,"language":272,"meta":273,"style":273},"\u002F\u002F src\u002Flib\u002Fapi.ts\n\nexport async function getFeatureFlags() {\n  try {\n    const response = await fetch(\"\u002F.netlify\u002Ffunctions\u002Ffeature-flags\");\n    if (!response.ok) {\n      throw new Error(\"Failed to fetch flags\");\n    }\n\n    const data = await response.json();\n    return { success: true, data: data.features as Feature[] };\n  } catch (error) {\n    return {\n      success: false,\n      error: error instanceof Error ? error.message : \"Unknown error\",\n    };\n  }\n}\n\n\u002F\u002F and, so on...\n",[58,2824,2825,2830,2834,2847,2853,2872,2882,2897,2901,2905,2921,2943,2951,2957,2967,2987,2991,2995,2999,3003],{"__ignoreMap":273},[277,2826,2827],{"class":279,"line":280},[277,2828,2829],{"class":283},"\u002F\u002F src\u002Flib\u002Fapi.ts\n",[277,2831,2832],{"class":279,"line":287},[277,2833,291],{"emptyLinePlaceholder":290},[277,2835,2836,2838,2840,2842,2845],{"class":279,"line":294},[277,2837,627],{"class":297},[277,2839,2324],{"class":297},[277,2841,747],{"class":297},[277,2843,2844],{"class":332}," getFeatureFlags",[277,2846,336],{"class":301},[277,2848,2849,2851],{"class":279,"line":315},[277,2850,1104],{"class":297},[277,2852,875],{"class":301},[277,2854,2855,2857,2859,2861,2863,2865,2867,2870],{"class":279,"line":321},[277,2856,1111],{"class":297},[277,2858,1114],{"class":649},[277,2860,763],{"class":297},[277,2862,1119],{"class":297},[277,2864,1122],{"class":332},[277,2866,1125],{"class":301},[277,2868,2869],{"class":308},"\"\u002F.netlify\u002Ffunctions\u002Ffeature-flags\"",[277,2871,1279],{"class":301},[277,2873,2874,2876,2878,2880],{"class":279,"line":326},[277,2875,1241],{"class":297},[277,2877,1244],{"class":301},[277,2879,1208],{"class":297},[277,2881,1249],{"class":301},[277,2883,2884,2886,2888,2890,2892,2895],{"class":279,"line":339},[277,2885,1254],{"class":297},[277,2887,1257],{"class":297},[277,2889,1260],{"class":332},[277,2891,1125],{"class":301},[277,2893,2894],{"class":308},"\"Failed to fetch flags\"",[277,2896,1279],{"class":301},[277,2898,2899],{"class":279,"line":348},[277,2900,1284],{"class":301},[277,2902,2903],{"class":279,"line":374},[277,2904,291],{"emptyLinePlaceholder":290},[277,2906,2907,2909,2911,2913,2915,2917,2919],{"class":279,"line":385},[277,2908,1111],{"class":297},[277,2910,1295],{"class":649},[277,2912,763],{"class":297},[277,2914,1119],{"class":297},[277,2916,1306],{"class":301},[277,2918,1309],{"class":332},[277,2920,1312],{"class":301},[277,2922,2923,2925,2928,2931,2934,2937,2940],{"class":279,"line":396},[277,2924,1087],{"class":297},[277,2926,2927],{"class":301}," { success: ",[277,2929,2930],{"class":649},"true",[277,2932,2933],{"class":301},", data: data.features ",[277,2935,2936],{"class":297},"as",[277,2938,2939],{"class":332}," Feature",[277,2941,2942],{"class":301},"[] };\n",[277,2944,2945,2947,2949],{"class":279,"line":407},[277,2946,1396],{"class":301},[277,2948,1399],{"class":297},[277,2950,1402],{"class":301},[277,2952,2953,2955],{"class":279,"line":432},[277,2954,1087],{"class":297},[277,2956,875],{"class":301},[277,2958,2959,2962,2965],{"class":279,"line":453},[277,2960,2961],{"class":301},"      success: ",[277,2963,2964],{"class":649},"false",[277,2966,659],{"class":301},[277,2968,2969,2972,2974,2976,2978,2980,2982,2985],{"class":279,"line":474},[277,2970,2971],{"class":301},"      error: error ",[277,2973,2746],{"class":297},[277,2975,1260],{"class":332},[277,2977,2751],{"class":297},[277,2979,2754],{"class":301},[277,2981,959],{"class":297},[277,2983,2984],{"class":308}," \"Unknown error\"",[277,2986,659],{"class":301},[277,2988,2989],{"class":279,"line":495},[277,2990,1377],{"class":301},[277,2992,2993],{"class":279,"line":504},[277,2994,1095],{"class":301},[277,2996,2997],{"class":279,"line":515},[277,2998,616],{"class":301},[277,3000,3001],{"class":279,"line":526},[277,3002,291],{"emptyLinePlaceholder":290},[277,3004,3005],{"class":279,"line":532},[277,3006,3007],{"class":283},"\u002F\u002F and, so on...\n",[10,3009,3010,3011,3014,3015,3018],{},"The above code snippets capture how the ",[58,3012,3013],{},"DevCycle SDK"," and its ",[58,3016,3017],{},"Management APIs"," are used within the app. You can go through the shared source code to view the complete implementation in more detail.",[23,3020,3022],{"id":3021},"wrapping-up","Wrapping Up",[10,3024,3025],{},"Brew Haven isn’t just a coffee shop app — it’s a showcase of how feature flags can make your projects more dynamic, responsive, and fun.",[10,3027,3028],{},"Feature flags allow you to:",[70,3030,3031,3034,3037,3040],{},[73,3032,3033],{},"Do gradual rollouts.",[73,3035,3036],{},"Reduce deployment risks.",[73,3038,3039],{},"Enable rapid experimentation.",[73,3041,3042],{},"Personalize user experiences.",[10,3044,3045],{},"Next time you’re sipping coffee and dreaming big, think about how feature flags can brew innovation into your projects. ☕",[3047,3048],"hr",{},[10,3050,3051],{},"Thank you for sticking with me until the end! I hope you’ve picked up some new concepts along the way. I’d love to hear what you learned or any thoughts you have in the comments section. Your feedback is not only valuable to me, but to the entire developer community exploring this exciting field.",[10,3053,3054],{},"Until next time.",[17,3056,3057],{},[10,3058,3059],{},[3060,3061,3062],"em",{},"Keep adding the bits, and soon you'll have a lot of bytes to share with the world.",[3064,3065,3066],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":273,"searchDepth":287,"depth":287,"links":3068},[3069,3073,3077,3081],{"id":25,"depth":287,"text":26,"children":3070},[3071,3072],{"id":33,"depth":294,"text":34},{"id":64,"depth":294,"text":65},{"id":87,"depth":287,"text":88,"children":3074},[3075,3076],{"id":116,"depth":294,"text":117},{"id":210,"depth":294,"text":211},{"id":240,"depth":287,"text":241,"children":3078},[3079,3080],{"id":250,"depth":294,"text":251},{"id":910,"depth":294,"text":911},{"id":3021,"depth":287,"text":3022},"https:\u002F\u002Fdev.to\u002Fra_jeeves\u002Fbuilding-brew-haven-ab-testing-my-coffee-shop-dreams-with-devcycle-401k","\u002Fimages\u002Fposts\u002Fbuilding-brew-haven-ab-testing-my-coffee-shop-dreams-with-devcycle\u002F1b223b14-15c4-401a-b08c-bb3816a5a85a-8d7979e641.png","2024-12-29T11:09:52.011Z","Do you love coffee? As developers, many of us jokingly claim to be \"powered by coffee\", and the thought of opening a quaint coffee shop someday often lingers in the back of our...",false,"md","cm59idr0r003e09kvhj3h7q4c",{},"\u002Fbuilding-brew-haven-ab-testing-my-coffee-shop-dreams-with-devcycle",{"title":5,"description":3085},"building-brew-haven-ab-testing-my-coffee-shop-dreams-with-devcycle",[3094,3095,3096,3097],"webdev","reactjs","feature-flags","devcycle","N-4hkgmBw0CmTvp2_Qm9rJ9Qa9Qzg9LYrGJaXnzOGVI",1780470200095]