Skip to content

Commit e1b7b48

Browse files
GrowthBook Flags SDK example (#1134)
1 parent 0a10fce commit e1b7b48

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+6141
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
# Misc
77
.DS_Store
88
*.pem
9+
.idea
910

1011
# Debug
1112
/npm-debug.log*

flags-sdk/growthbook/.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# node -e 'console.log(`${crypto.randomBytes(32).toString("base64url")}`)'
2+
FLAGS_SECRET=""
3+
GROWTHBOOK_CLIENT_KEY=""
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"root": true,
3+
"extends": "next/core-web-vitals",
4+
"rules": {
5+
"@typescript-eslint/require-await": "off",
6+
"@typescript-eslint/no-misused-promises": "off",
7+
"import/order": "off",
8+
"camelcase": "off",
9+
"no-console": "off"
10+
}
11+
}

flags-sdk/growthbook/.gitignore

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
.yarn/install-state.gz
8+
9+
# testing
10+
/coverage
11+
12+
# next.js
13+
/.next/
14+
/out/
15+
16+
# production
17+
/build
18+
19+
# misc
20+
.DS_Store
21+
*.pem
22+
.idea
23+
24+
# debug
25+
npm-debug.log*
26+
yarn-debug.log*
27+
yarn-error.log*
28+
29+
# local env files
30+
.env*.local
31+
32+
# vercel
33+
.vercel
34+
35+
# typescript
36+
*.tsbuildinfo
37+
next-env.d.ts

flags-sdk/growthbook/README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Growthbook Flags SDK Example
2+
3+
This example uses GrowthBook for feature flags with the [Flags SDK](https://flags-sdk.dev) along with the `@flags-sdk/growthbook` [GrowthBook adapter](https://flags-sdk.dev/providers/growthbook) and the [Flags Explorer](https://vercel.com/docs/workflow-collaboration/feature-flags/using-vercel-toolbar).
4+
5+
## Demo
6+
7+
[https://flags-sdk-growthbook.vercel.app/](https://flags-sdk-growthbook.vercel.app/)
8+
9+
## How it works
10+
11+
This demo controls the visibility of two banners on the home page, and the color of the checkout button.
12+
13+
Once you visit the page, you can see a variation of both/one/none of the banners.
14+
15+
To test different variations, you can use the Dev Tools at the bottom to reset the stable id and reload the page.
16+
17+
When you create and link a project on Vercel, you may use the [Flags Explorer](https://vercel.com/docs/workflow-collaboration/feature-flags/using-vercel-toolbar) to see what variants are active, and test different variations by creating overrides.
18+
19+
## Deploy this template
20+
21+
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fexamples%2Ftree%2Fmain%2Fflags-sdk%2Fgrowthbook&env=GROWTHBOOK_CLIENT_KEY&env=FLAGS_SECRET&envDescription=The+FLAGS_SECRET+will+be+used+by+the+Flags+Explorer+to+securely+overwrite+feature+flags.+Must+be+32+random+bytes%2C+base64-encoded.+Use+the+generated+value+or+set+your+own.&envLink=https%3A%2F%2Fvercel.com%2Fdocs%2Fworkflow-collaboration%2Ffeature-flags%2Fsupporting-feature-flags%23flags_secret-environment-variable&project-name=growthbook-flags-sdk-example&repository-name=growthbook-flags-sdk-example)
22+
23+
### Step 1: Link the project
24+
25+
In order to use the Flags Explorer, you need to link the project on your local machine.
26+
27+
```bash
28+
vercel link
29+
```
30+
31+
If your project does not exist yet, you will be prompted to create it.
32+
33+
### Step 2: Pull all environment variables
34+
35+
This allows the Flags SDK and the Flags Explorer to work correctly, by getting additional metadata.
36+
37+
```bash
38+
vercel env pull
39+
```
40+
41+
If you are building this on the CLI, you can set the environment variables with `vercel env add`
42+
43+
### Step 3: Create Feature Gates and Experiments
44+
45+
On GrowthBook, create a project with the following flags split by `id`:
46+
47+
- `free_delivery` (50% rollout or experiment)
48+
- `summer_sale` (50% rollout or experiment)
49+
- `proceed_to_checkout` (Multiple variants serving `red`, `green` or `blue`)
50+
51+
The id of the flags in GrowthBook should match the key of the flags in the `flags.ts` file.
52+
53+
If you have not started your experiments yet, you must do so in order to see the traffic split.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { getProviderData as getGrowthbookProviderData } from '@flags-sdk/growthbook'
2+
import { type ProviderData, mergeProviderData } from 'flags'
3+
import { createFlagsDiscoveryEndpoint, getProviderData } from 'flags/next'
4+
import * as flags from '../../../../flags'
5+
6+
export const GET = createFlagsDiscoveryEndpoint(async () => {
7+
// Fetches additional metadata from the GrowthBook API for the Flags Explorer
8+
let growthbookData: ProviderData = { definitions: {}, hints: [] }
9+
if (process.env.GROWTHBOOK_API_KEY) {
10+
growthbookData = await getGrowthbookProviderData({
11+
apiKey: process.env.GROWTHBOOK_API_KEY,
12+
clientKey: process.env.GROWTHBOOK_CLIENT_KEY,
13+
})
14+
}
15+
16+
return mergeProviderData([getProviderData(flags), growthbookData])
17+
})
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use client'
2+
3+
import { useState } from 'react'
4+
import { useRouter } from 'next/navigation'
5+
import { addToCart } from '@/lib/actions'
6+
import { useProductDetailPageContext } from '@/components/utils/product-detail-page-context'
7+
import { AddToCartButton } from '@/components/product-detail-page/add-to-cart-button'
8+
9+
export function AddToCart() {
10+
const router = useRouter()
11+
const { color, size } = useProductDetailPageContext()
12+
const [isLoading, setIsLoading] = useState(false)
13+
14+
return (
15+
<AddToCartButton
16+
isLoading={isLoading}
17+
onClick={async () => {
18+
setIsLoading(true)
19+
await addToCart({ id: 'shirt', color, size, quantity: 1 })
20+
router.push('/cart')
21+
}}
22+
/>
23+
)
24+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { proceedToCheckoutColorFlag } from '@/flags'
2+
import { OrderSummarySection } from '@/components/shopping-cart/order-summary-section'
3+
import { ProceedToCheckout } from './proceed-to-checkout'
4+
import { FlagValues } from 'flags/react'
5+
6+
export async function OrderSummary({
7+
showSummerBanner,
8+
freeDelivery,
9+
}: {
10+
showSummerBanner: boolean
11+
freeDelivery: boolean
12+
}) {
13+
// This is a fast feature flag so we don't suspend on it
14+
const proceedToCheckoutColor = await proceedToCheckoutColorFlag()
15+
16+
return (
17+
<OrderSummarySection
18+
showSummerBanner={showSummerBanner}
19+
freeDelivery={freeDelivery}
20+
proceedToCheckout={
21+
<>
22+
<ProceedToCheckout color={proceedToCheckoutColor} />
23+
<FlagValues
24+
values={{
25+
[proceedToCheckoutColorFlag.key]: proceedToCheckoutColor,
26+
}}
27+
/>
28+
</>
29+
}
30+
/>
31+
)
32+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { OrderSummary } from '@/app/[code]/cart/order-summary'
2+
import { Main } from '@/components/main'
3+
import { ShoppingCart } from '@/components/shopping-cart/shopping-cart'
4+
import {
5+
productFlags,
6+
showFreeDeliveryBannerFlag,
7+
showSummerBannerFlag,
8+
} from '@/flags'
9+
10+
export default async function CartPage({
11+
params,
12+
}: {
13+
params: Promise<{ code: string }>
14+
}) {
15+
const { code } = await params
16+
const showSummerBanner = await showSummerBannerFlag(code, productFlags)
17+
const freeDeliveryBanner = await showFreeDeliveryBannerFlag(
18+
code,
19+
productFlags
20+
)
21+
22+
return (
23+
<Main>
24+
<div className="lg:grid lg:grid-cols-12 lg:items-start lg:gap-x-12 xl:gap-x-16">
25+
<ShoppingCart />
26+
<OrderSummary
27+
showSummerBanner={showSummerBanner}
28+
freeDelivery={freeDeliveryBanner}
29+
/>
30+
</div>
31+
</Main>
32+
)
33+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use client'
2+
3+
import { ProceedToCheckoutButton } from '@/components/shopping-cart/proceed-to-checkout-button'
4+
import { toast } from 'sonner'
5+
6+
export function ProceedToCheckout({ color }: { color: string }) {
7+
return (
8+
<>
9+
<ProceedToCheckoutButton
10+
color={color}
11+
onClick={() => {
12+
// Auto capture will track the event
13+
toast('End reached', {
14+
className: 'my-classname',
15+
description:
16+
'The checkout flow is not implemented in this template.',
17+
duration: 5000,
18+
})
19+
}}
20+
/>
21+
</>
22+
)
23+
}

0 commit comments

Comments
 (0)