Skip to content
Merged
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
6 changes: 6 additions & 0 deletions flags-sdk/flagsmith/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# For @flags-sdk/flagsmith
# node -e 'console.log(`${crypto.randomBytes(32).toString("base64url")}`)'
FLAGS_SECRET=""

FLAGSMITH_ENVIRONMENT_ID=""
FLAGSMITH_PROJECT_ID=""
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"
}
}
37 changes: 37 additions & 0 deletions flags-sdk/flagsmith/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# 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
.env
49 changes: 49 additions & 0 deletions flags-sdk/flagsmith/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Flagsmith Flags SDK Example

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).

## Demo

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

## How it works

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

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.

## Deploy this template

[![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)

### Step 1: Link the project

In order to use the Flags Explorer, you need to link the project on your local machine.

```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](https://flagsmith.com) 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`

You can also find the feature flag keys in the `flags.ts` file.

Set both feature flags to rollout to 50% of users.
23 changes: 23 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,23 @@
import { getProviderData, createFlagsDiscoveryEndpoint } from 'flags/next'
import { getProviderData as getFlagSmithProviderData } from '@flags-sdk/flagsmith'
import { mergeProviderData } from 'flags'
import * as flags from '../../../../flags'

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

export const GET = createFlagsDiscoveryEndpoint(
async () => {
return mergeProviderData([
// Data declared from Flags in Code
getProviderData(flags),
// metadata from Flagsmith API using the default flagsmith adapter
getFlagSmithProviderData({
environmentKey: process.env.FLAGSMITH_ENVIRONMENT_ID as string,
projectId: process.env.FLAGSMITH_PROJECT_ID as string,
}),
])
},
{
secret: process.env.FLAGS_SECRET,
}
)
24 changes: 24 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,24 @@
'use client'

import { 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)

return (
<AddToCartButton
isLoading={isLoading}
onClick={async () => {
setIsLoading(true)
await addToCart({ id: 'shirt', color, size, quantity: 1 })
router.push('/cart')
}}
/>
)
}
18 changes: 18 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,18 @@
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
}) {
return (
<OrderSummarySection
showSummerBanner={showSummerBanner}
freeDelivery={freeDelivery}
proceedToCheckout={<ProceedToCheckout color={'blue'} />}
/>
)
}
35 changes: 35 additions & 0 deletions flags-sdk/flagsmith/app/[code]/cart/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
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
)

console.log('freeDeliveryBanner', freeDeliveryBanner)

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>
)
}
24 changes: 24 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,24 @@
'use client'

import { ProceedToCheckoutButton } from '@/components/shopping-cart/proceed-to-checkout-button'

import { toast } from 'sonner'

export function ProceedToCheckout({ color }: { color: string }) {
return (
<>
<ProceedToCheckoutButton
color={color}
onClick={() => {
// Auto capture will track the event
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>
)
}
37 changes: 37 additions & 0 deletions flags-sdk/flagsmith/app/[code]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { AddToCart } from '@/app/[code]/add-to-cart'
import { SummerSale } from '@/app/summer-sale'
import { ImageGallery } from '@/components/image-gallery'
import { ColorPicker } from '@/components/product-detail-page/color-picker'
import { ProductDetails } from '@/components/product-detail-page/product-details'
import { ProductHeader } from '@/components/product-detail-page/product-header'
import { SizePicker } from '@/components/product-detail-page/size-picker'
import { ProductDetailPageProvider } from '@/components/utils/product-detail-page-context'

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

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} gate={showSummerBannerFlag.key} />
<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.
7 changes: 7 additions & 0 deletions flags-sdk/flagsmith/app/free-delivery.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use client'

import { FreeDeliveryBanner } from '@/components/banners/free-delivery-banner'

export function FreeDelivery({ show }: { show: boolean }) {
return <>{show ? <FreeDeliveryBanner /> : null}</>
}
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);
}
28 changes: 28 additions & 0 deletions flags-sdk/flagsmith/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { VercelToolbar } from '@vercel/toolbar/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: 'Flagsmith - Flags SDK Example',
description: 'A Flags SDK ecommerce example using Flagsmith',
}

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en">
<body className="antialiased">
<ExamplesBanner />
{children}
<Toaster />
<VercelToolbar />
</body>
</html>
)
}
Binary file added flags-sdk/flagsmith/app/opengraph-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions flags-sdk/flagsmith/app/summer-sale.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use client'

import { toast } from 'sonner'
import { SummerSaleBanner } from '@/components/banners/summer-sale-banner'

export function SummerSale({ gate, show }: { show: boolean; gate: string }) {
return (
<>
{show && (
<SummerSaleBanner
onClick={() => {
toast('End reached', {
className: 'my-classname',
description:
'The summer sale is not implemented in this template. Try adding to the cart instead.',
})
}}
/>
)}
</>
)
}
Loading