-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
tiagoapolo
wants to merge
5
commits into
vercel:main
Choose a base branch
from
tiagoapolo:flagsmith-example
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+4,247
−0
Open
Changes from 4 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
70f3087
feat: Adds Flagsmith example
tiagoapolo 2b986f7
remove openfeature names
tiagoapolo e66ed0f
Update flags-sdk/flagsmith/flags.ts
tiagoapolo 790241c
address comments
tiagoapolo 8b519a7
Merge branch 'main' into flagsmith-example
tiagoapolo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
||
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') | ||
}} | ||
/> | ||
) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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} />} | ||
/> | ||
) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
21
flags-sdk/flagsmith/app/[code]/cart/proceed-to-checkout.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}) | ||
}} | ||
/> | ||
) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 /> | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
) | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
AddToCart
component sets loading state totrue
but never resets it tofalse
if theaddToCart
API call fails, leaving the button permanently in a loading state.View Details
📝 Patch Details
Analysis
In the
AddToCart
component, theonClick
handler setsisLoading
totrue
at the start but has no error handling to reset it if theaddToCart
API call fails. IfaddToCart
throws an error (network failure, server error, etc.), the component will remain in the loading state indefinitely, making the button unusable.This creates a poor user experience where users can't retry the action after a network failure, and the UI provides no feedback about what went wrong.
Recommendation
Wrap the API call in a try-catch block and ensure
setIsLoading(false)
is called in the catch block: