Skip to content

feat: Adds Flagsmith example #1144

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions flags-sdk/flagsmith/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"root": true,
"extends": "next/core-web-vitals",
"rules": {
"@typescript-eslint/require-await": "off",
"@typescript-eslint/no-misused-promises": "off",
"import/order": "off",
"camelcase": "off",
"no-console": "off"
}
}
36 changes: 36 additions & 0 deletions flags-sdk/flagsmith/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
66 changes: 66 additions & 0 deletions flags-sdk/flagsmith/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# flagsmith Flags SDK Example

This example uses [flagsmith](https://flagsmith.com/) for feature flags with the [Flags SDK](https://flags-sdk.dev) and the [Flags SDK flagsmith adapter](https://flags-sdk.dev/docs/api-reference/adapters/flagsmith) along with the [Flags Explorer](https://vercel.com/docs/workflow-collaboration/feature-flags/using-vercel-toolbar).

## Demo

[https://flags-sdk-flagsmith.vercel.app/](https://flags-sdk-flagsmith.vercel.app/)

## How it works

This demo uses two feature flags defined in code control the visibility of two banners on the page.
Both flags are configured to show/hide each banner 50% of the time.

Once you visit the page, you can see a variation of both/one/none of the banners.
Since this example is using a stable id to identify users, you will see the same variation until you reset your id.

To test different variations, you can use the Dev Tools at the bottom to reset the stable id and reload the page.
This allows you to test different variations of the banners.

If you deployed your own instance of this example you can also use the [Flags Explorer](https://vercel.com/docs/workflow-collaboration/feature-flags/using-vercel-toolbar) to test different variations by creating overrides.

## Deploy this template

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.51.al%2Fvercel%2Fexamples%2Ftree%2Fmain%2Fflags-sdk%2Fflagsmith&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=flagsmith-flags-sdk-example&repository-name=flagsmith-flags-sdk-example)

### Step 1: Link the project

First, install the latest version of the Vercel CLI:

```bash
pnpm i -g vercel
```

or

```bash
npm i -g vercel
```

To use the Flags Explorer, you need to connect your local project to your Vercel project:

```bash
vercel link
```

Select the project from the list you just deployed.

### Step 2: Pull all environment variables

This allows the Flags SDK and the Flags Explorer to work correctly, by getting additional metadata.

```bash
vercel env pull
```

Step 3: Create Feature Flags
Head over to Flagsmith dashboard and create the feature flags required by this template.

Feature Flags:

Summer Sale with the key summer-sale
Free Shipping with the key free-delivery
Proceed To Checkout with the key proceed-to-checkout-color
You can also find the feature flag keys in the flags.ts file.

Set both feature flags to rollout to 50% of users.
6 changes: 6 additions & 0 deletions flags-sdk/flagsmith/app/.well-known/vercel/flags/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createFlagsDiscoveryEndpoint, getProviderData } from 'flags/next'
import * as flags from '../../../../flags'

export const dynamic = 'force-dynamic' // defaults to auto

export const GET = createFlagsDiscoveryEndpoint(() => getProviderData(flags))
30 changes: 30 additions & 0 deletions flags-sdk/flagsmith/app/[code]/add-to-cart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use client'

import { track } from '@vercel/analytics'
import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
import { addToCart } from '@/lib/actions'
import { useProductDetailPageContext } from '@/components/utils/product-detail-page-context'
import { AddToCartButton } from '@/components/product-detail-page/add-to-cart-button'

export function AddToCart() {
const router = useRouter()
const { color, size } = useProductDetailPageContext()
const [isLoading, setIsLoading] = useState(false)

useEffect(() => {
track('add_to_cart:viewed')
}, [])

return (
<AddToCartButton
isLoading={isLoading}
onClick={async () => {
setIsLoading(true)
track('add_to_cart:clicked')
await addToCart({ id: 'shirt', color, size, quantity: 1 })
router.push('/cart')
}}
/>
)
}
22 changes: 22 additions & 0 deletions flags-sdk/flagsmith/app/[code]/cart/order-summary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { proceedToCheckoutColorFlag } from '@/flags'
import { OrderSummarySection } from '@/components/shopping-cart/order-summary-section'
import { ProceedToCheckout } from './proceed-to-checkout'

export async function OrderSummary({
showSummerBanner,
freeDelivery,
}: {
showSummerBanner: boolean
freeDelivery: boolean
}) {
// This is a fast feature flag so we don't suspend on it
const proceedToCheckoutColor = await proceedToCheckoutColorFlag()

return (
<OrderSummarySection
showSummerBanner={showSummerBanner}
freeDelivery={freeDelivery}
proceedToCheckout={<ProceedToCheckout color={proceedToCheckoutColor} />}
/>
)
}
33 changes: 33 additions & 0 deletions flags-sdk/flagsmith/app/[code]/cart/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { OrderSummary } from '@/app/[code]/cart/order-summary'
import { Main } from '@/components/main'
import { ShoppingCart } from '@/components/shopping-cart/shopping-cart'
import {
productFlags,
showFreeDeliveryBannerFlag,
showSummerBannerFlag,
} from '@/flags'

export default async function CartPage({
params,
}: {
params: Promise<{ code: string }>
}) {
const { code } = await params
const showSummerBanner = await showSummerBannerFlag(code, productFlags)
const freeDeliveryBanner = await showFreeDeliveryBannerFlag(
code,
productFlags
)

return (
<Main>
<div className="lg:grid lg:grid-cols-12 lg:items-start lg:gap-x-12 xl:gap-x-16">
<ShoppingCart />
<OrderSummary
showSummerBanner={showSummerBanner}
freeDelivery={freeDeliveryBanner}
/>
</div>
</Main>
)
}
21 changes: 21 additions & 0 deletions flags-sdk/flagsmith/app/[code]/cart/proceed-to-checkout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use client'

import { ProceedToCheckoutButton } from '@/components/shopping-cart/proceed-to-checkout-button'
import { track } from '@vercel/analytics'
import { toast } from 'sonner'

export function ProceedToCheckout({ color }: { color: string }) {
return (
<ProceedToCheckoutButton
color={color}
onClick={() => {
track('proceed_to_checkout:clicked')
toast('End reached', {
className: 'my-classname',
description: 'The checkout flow is not implemented in this template.',
duration: 5000,
})
}}
/>
)
}
45 changes: 45 additions & 0 deletions flags-sdk/flagsmith/app/[code]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { deserialize, generatePermutations } from 'flags/next'
import { FlagValues } from 'flags/react'
import { productFlags, showFreeDeliveryBannerFlag } from '@/flags'
import { FreeDelivery } from '@/app/free-delivery'
import { DevTools } from '@/components/dev-tools'
import { Footer } from '@/components/footer'
import { Navigation } from '@/components/navigation'

export async function generateStaticParams() {
// Returning an empty array here is important as it enables ISR, so
// the various combinations stay cached after they first time they were rendered.
//
// return [];

// Instead of returning an empty array you could also call generatePermutations
// to generate the permutations upfront.
const codes = await generatePermutations(productFlags)
return codes.map((code) => ({ code }))
}

export default async function Layout(props: {
children: React.ReactNode
params: Promise<{
code: string
}>
}) {
const params = await props.params
const values = await deserialize(productFlags, params.code)

const showFreeDeliveryBanner = await showFreeDeliveryBannerFlag(
params.code,
productFlags
)

return (
<div className="bg-white">
<FreeDelivery show={showFreeDeliveryBanner} />
<Navigation />
{props.children}
<FlagValues values={values} />
<Footer />
<DevTools />
</div>
)
}
38 changes: 38 additions & 0 deletions flags-sdk/flagsmith/app/[code]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { SummerSale } from '@/app/summer-sale'
import { ImageGallery } from '@/components/image-gallery'
import { ProductDetails } from '@/components/product-detail-page/product-details'
import { ProductHeader } from '@/components/product-detail-page/product-header'
import { AddToCart } from '@/app/[code]/add-to-cart'
import { ColorPicker } from '@/components/product-detail-page/color-picker'
import { SizePicker } from '@/components/product-detail-page/size-picker'
import { ProductDetailPageProvider } from '@/components/utils/product-detail-page-context'

import { productFlags, showSummerBannerFlag } from '@/flags'
import { Main } from '@/components/main'

export default async function Page(props: {
params: Promise<{ code: string }>
}) {
const params = await props.params

const showSummerBanner = await showSummerBannerFlag(params.code, productFlags)

return (
<ProductDetailPageProvider>
<SummerSale show={showSummerBanner} />
<Main>
<div className="lg:grid lg:auto-rows-min lg:grid-cols-12 lg:gap-x-8">
<ProductHeader />
<ImageGallery />

<div className="mt-8 lg:col-span-5">
<ColorPicker />
<SizePicker />
<AddToCart />
<ProductDetails />
</div>
</div>
</Main>
</ProductDetailPageProvider>
)
}
Binary file added flags-sdk/flagsmith/app/favicon.ico
Binary file not shown.
15 changes: 15 additions & 0 deletions flags-sdk/flagsmith/app/free-delivery.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use client'

import { FreeDeliveryBanner } from '@/components/banners/free-delivery-banner'
import { track } from '@vercel/analytics'
import { useEffect } from 'react'

export function FreeDelivery(props: { show: boolean }) {
useEffect(() => {
if (props.show) track('free_delivery_banner:viewed')
}, [props.show])

if (!props.show) return null

return <FreeDeliveryBanner />
}
10 changes: 10 additions & 0 deletions flags-sdk/flagsmith/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@import 'tailwindcss';

@custom-variant dark (&:is(.dark *));

@theme {
--color-link: rgb(0 112 243 / 1);
--color-success: rgb(0 112 243 / 1);
--color-background: white;
--color-success-dark: rgb(7 97 209 / 1);
}
30 changes: 30 additions & 0 deletions flags-sdk/flagsmith/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { VercelToolbar } from '@vercel/toolbar/next'
import { Analytics } from '@vercel/analytics/next'
import type { Metadata } from 'next'
import { Toaster } from 'sonner'

import './globals.css'
import { ExamplesBanner } from '@/components/banners/examples-banner'

export const metadata: Metadata = {
title: 'Flags SDK Example',
description: 'A Flags SDK example for Ecommerce',
}

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en">
<body className="antialiased">
<ExamplesBanner />
{children}
<Toaster />
<Analytics />
<VercelToolbar />
</body>
</html>
)
}
Loading