Skip to content

Commit 7abf4d4

Browse files
authored
Add flags-sdk/flagsmith example (#1289)
Uses the `@flags-sdk/flagsmith` adapter
1 parent 464734c commit 7abf4d4

Some content is hidden

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

56 files changed

+5975
-0
lines changed

flags-sdk/flagsmith/.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# For @flags-sdk/flagsmith
2+
# node -e 'console.log(`${crypto.randomBytes(32).toString("base64url")}`)'
3+
FLAGS_SECRET=""
4+
5+
FLAGSMITH_ENVIRONMENT_ID=""
6+
FLAGSMITH_PROJECT_ID=""

flags-sdk/flagsmith/.eslintrc.json

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/flagsmith/.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+
23+
# debug
24+
npm-debug.log*
25+
yarn-debug.log*
26+
yarn-error.log*
27+
28+
# local env files
29+
.env*.local
30+
31+
# vercel
32+
.vercel
33+
34+
# typescript
35+
*.tsbuildinfo
36+
next-env.d.ts
37+
.env

flags-sdk/flagsmith/README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Flagsmith Flags SDK Example
2+
3+
This example uses [Flagsmith](https://flagsmith.com) for feature flags with the [Flags SDK](https://flags-sdk.dev) along with the `@flags-sdk/flagsmith` [Flagsmith adapter](https://flags-sdk.dev/providers/flagsmith) and the [Flags Explorer](https://vercel.com/docs/workflow-collaboration/feature-flags/using-vercel-toolbar).
4+
5+
## Demo
6+
7+
[https://flags-sdk-flagsmith.vercel.app/](https://flags-sdk-flagsmith.vercel.app/)
8+
9+
## How it works
10+
11+
This demo uses two feature flags in Flagsmith to control the visibility of two banners on the page.
12+
Both gates are configured to show/hide each banner 50% of the time.
13+
14+
If you deploy your own and configure the feature flags in Flagsmith, you can also use the [Flags Explorer](https://vercel.com/docs/workflow-collaboration/feature-flags/using-vercel-toolbar) to enabled/disabled the features.
15+
16+
## Deploy this template
17+
18+
[![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/flagsmith&env=FLAGS_SECRET&env=FLAGSMITH_ENVIRONMENT_ID&env=FLAGSMITH_PROJECT_ID&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=flagsmith-flags-sdk-example&repository-name=flagsmith-flags-sdk-example)
19+
20+
### Step 1: Link the project
21+
22+
In order to use the Flags Explorer, you need to link the project on your local machine.
23+
24+
```bash
25+
vercel link
26+
```
27+
28+
Select the project from the list you just deployed.
29+
30+
### Step 2: Pull all environment variables
31+
32+
This allows the Flags SDK and the Flags Explorer to work correctly, by getting additional metadata.
33+
34+
```bash
35+
vercel env pull
36+
```
37+
38+
### Step 3: Create Feature Flags
39+
40+
Head over to [Flagsmith](https://flagsmith.com) and create the feature flags required by this template.
41+
42+
Feature Flags:
43+
44+
- `Summer Sale` with the key `summer_sale`
45+
- `Free Shipping` with the key `free_delivery`
46+
47+
You can also find the feature flag keys in the `flags.ts` file.
48+
49+
Set both feature flags to rollout to 50% of users.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { getProviderData, createFlagsDiscoveryEndpoint } from 'flags/next'
2+
import { getProviderData as getFlagSmithProviderData } from '@flags-sdk/flagsmith'
3+
import { mergeProviderData } from 'flags'
4+
import * as flags from '../../../../flags'
5+
6+
export const dynamic = 'force-dynamic' // defaults to auto
7+
8+
export const GET = createFlagsDiscoveryEndpoint(
9+
async () => {
10+
return mergeProviderData([
11+
// Data declared from Flags in Code
12+
getProviderData(flags),
13+
// metadata from Flagsmith API using the default flagsmith adapter
14+
getFlagSmithProviderData({
15+
environmentKey: process.env.FLAGSMITH_ENVIRONMENT_ID as string,
16+
projectId: process.env.FLAGSMITH_PROJECT_ID as string,
17+
}),
18+
])
19+
},
20+
{
21+
secret: process.env.FLAGS_SECRET,
22+
}
23+
)
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: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { OrderSummarySection } from '@/components/shopping-cart/order-summary-section'
2+
import { ProceedToCheckout } from './proceed-to-checkout'
3+
4+
export async function OrderSummary({
5+
showSummerBanner,
6+
freeDelivery,
7+
}: {
8+
showSummerBanner: boolean
9+
freeDelivery: boolean
10+
}) {
11+
return (
12+
<OrderSummarySection
13+
showSummerBanner={showSummerBanner}
14+
freeDelivery={freeDelivery}
15+
proceedToCheckout={<ProceedToCheckout color={'blue'} />}
16+
/>
17+
)
18+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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+
console.log('freeDeliveryBanner', freeDeliveryBanner)
23+
24+
return (
25+
<Main>
26+
<div className="lg:grid lg:grid-cols-12 lg:items-start lg:gap-x-12 xl:gap-x-16">
27+
<ShoppingCart />
28+
<OrderSummary
29+
showSummerBanner={showSummerBanner}
30+
freeDelivery={freeDeliveryBanner}
31+
/>
32+
</div>
33+
</Main>
34+
)
35+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use client'
2+
3+
import { ProceedToCheckoutButton } from '@/components/shopping-cart/proceed-to-checkout-button'
4+
5+
import { toast } from 'sonner'
6+
7+
export function ProceedToCheckout({ color }: { color: string }) {
8+
return (
9+
<>
10+
<ProceedToCheckoutButton
11+
color={color}
12+
onClick={() => {
13+
// Auto capture will track the event
14+
toast('End reached', {
15+
className: 'my-classname',
16+
description:
17+
'The checkout flow is not implemented in this template.',
18+
duration: 5000,
19+
})
20+
}}
21+
/>
22+
</>
23+
)
24+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { deserialize, generatePermutations } from 'flags/next'
2+
import { FlagValues } from 'flags/react'
3+
import { productFlags, showFreeDeliveryBannerFlag } from '@/flags'
4+
import { FreeDelivery } from '@/app/free-delivery'
5+
import { DevTools } from '@/components/dev-tools'
6+
import { Footer } from '@/components/footer'
7+
import { Navigation } from '@/components/navigation'
8+
9+
export async function generateStaticParams() {
10+
// Returning an empty array here is important as it enables ISR, so
11+
// the various combinations stay cached after they first time they were rendered.
12+
//
13+
// return [];
14+
15+
// Instead of returning an empty array you could also call generatePermutations
16+
// to generate the permutations upfront.
17+
const codes = await generatePermutations(productFlags)
18+
return codes.map((code) => ({ code }))
19+
}
20+
21+
export default async function Layout(props: {
22+
children: React.ReactNode
23+
params: Promise<{
24+
code: string
25+
}>
26+
}) {
27+
const params = await props.params
28+
const values = await deserialize(productFlags, params.code)
29+
30+
const showFreeDeliveryBanner = await showFreeDeliveryBannerFlag(
31+
params.code,
32+
productFlags
33+
)
34+
35+
return (
36+
<div className="bg-white">
37+
<FreeDelivery show={showFreeDeliveryBanner} />
38+
<Navigation />
39+
{props.children}
40+
<FlagValues values={values} />
41+
<Footer />
42+
<DevTools />
43+
</div>
44+
)
45+
}

0 commit comments

Comments
 (0)