Skip to content

Commit bac6c5f

Browse files
committed
Initial commit
Created from https://vercel.com/new
0 parents  commit bac6c5f

Some content is hidden

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

55 files changed

+1724
-0
lines changed

.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+
}

.gitignore

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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

README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Shirt Shop Flags SDK Example
2+
3+
This example uses the [Flags SDK](https://flags-sdk.dev) along with the [Flags Explorer](https://vercel.com/docs/workflow-collaboration/feature-flags/using-vercel-toolbar) and `@vercel/analytics`.
4+
5+
## Demo
6+
7+
[https://shirt-shop.labs.vercel.dev/](https://shirt-shop.labs.vercel.dev/)
8+
9+
## How it works
10+
11+
This demo uses two feature flags defined in code control the visibility of two banners on the page.
12+
Both flags are configured to show/hide each banner 50% of the time.
13+
14+
Once you visit the page, you can see a variation of both/one/none of the banners.
15+
Since this example is using a stable id to identify users, you will see the same variation all the time.
16+
17+
To test different variations, you can use the Dev Tools at the bottom to reset the stable id and reload the page.
18+
This allows you to test different variations of the banners.
19+
20+
This templates also tracks analytics events in Vercel Analytics.
21+
22+
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.
23+
24+
## Deploy this template
25+
26+
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fflags%2Ftree%2Fmain%2Fexamples%2Fshirt-shop&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=shirt-shop-flags-sdk-example&repository-name=shirt-shop-flags-sdk-example)
27+
28+
### Step 1: Link the project
29+
30+
In order to use the Flags Explorer, you need to link the project on your local machine.
31+
32+
```bash
33+
vercel link
34+
```
35+
36+
Select the project from the list you just deployed.
37+
38+
### Step 2: Pull all environment variables
39+
40+
This allows the Flags SDK and the Flags Explorer to work correctly, by getting additional metadata.
41+
42+
```bash
43+
vercel env pull
44+
```
45+
46+
### Step 3: Install dependencies
47+
48+
```bash
49+
npm install
50+
```
51+
52+
### Step 4: Run the project
53+
54+
```bash
55+
npm run dev
56+
```
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { getProviderData, createFlagsDiscoveryEndpoint } from 'flags/next';
2+
import * as flags from '../../../../flags';
3+
4+
export const GET = createFlagsDiscoveryEndpoint(async (request) => {
5+
const providerData = await getProviderData(flags);
6+
return providerData;
7+
});

app/[code]/add-to-cart.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
'use client';
2+
3+
import { track } from '@vercel/analytics';
4+
import { useEffect, useState } from 'react';
5+
import { useRouter } from 'next/navigation';
6+
import { addToCart } from '@/lib/actions';
7+
import { useProductDetailPageContext } from '@/components/utils/product-detail-page-context';
8+
import { AddToCartButton } from '@/components/product-detail-page/add-to-cart-button';
9+
10+
export function AddToCart() {
11+
const router = useRouter();
12+
const { color, size } = useProductDetailPageContext();
13+
const [isLoading, setIsLoading] = useState(false);
14+
15+
useEffect(() => {
16+
track('add_to_cart:viewed');
17+
}, []);
18+
19+
return (
20+
<AddToCartButton
21+
isLoading={isLoading}
22+
onClick={async () => {
23+
setIsLoading(true);
24+
track('add_to_cart:clicked');
25+
await addToCart({ id: 'shirt', color, size, quantity: 1 });
26+
router.push('/cart');
27+
}}
28+
/>
29+
);
30+
}

app/[code]/cart/order-summary.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { proceedToCheckoutColorFlag } from '@/flags';
2+
import { OrderSummarySection } from '@/components/shopping-cart/order-summary-section';
3+
import { ProceedToCheckout } from './proceed-to-checkout';
4+
5+
export async function OrderSummary({
6+
showSummerBanner,
7+
freeDelivery,
8+
}: {
9+
showSummerBanner: boolean;
10+
freeDelivery: boolean;
11+
}) {
12+
// This is a fast feature flag so we don't suspend on it
13+
const proceedToCheckoutColor = await proceedToCheckoutColorFlag();
14+
15+
return (
16+
<OrderSummarySection
17+
showSummerBanner={showSummerBanner}
18+
freeDelivery={freeDelivery}
19+
proceedToCheckout={<ProceedToCheckout color={proceedToCheckoutColor} />}
20+
/>
21+
);
22+
}

app/[code]/cart/page.tsx

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: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use client';
2+
3+
import { ProceedToCheckoutButton } from '@/components/shopping-cart/proceed-to-checkout-button';
4+
import { track } from '@vercel/analytics';
5+
import { toast } from 'sonner';
6+
7+
export function ProceedToCheckout({ color }: { color: string }) {
8+
return (
9+
<ProceedToCheckoutButton
10+
color={color}
11+
onClick={() => {
12+
track('proceed_to_checkout:clicked');
13+
toast('End reached', {
14+
className: 'my-classname',
15+
description: 'The checkout flow is not implemented in this template.',
16+
duration: 5000,
17+
});
18+
}}
19+
/>
20+
);
21+
}

app/[code]/layout.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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+
import { encryptFlagValues } from 'flags';
9+
10+
export async function generateStaticParams() {
11+
// Returning an empty array here is important as it enables ISR, so
12+
// the various combinations stay cached after they first time they were rendered.
13+
//
14+
// return [];
15+
16+
// Instead of returning an empty array you could also call generatePermutations
17+
// to generate the permutations upfront.
18+
const codes = await generatePermutations(productFlags);
19+
return codes.map((code) => ({ code }));
20+
}
21+
22+
export default async function Layout(props: {
23+
children: React.ReactNode;
24+
params: Promise<{
25+
code: string;
26+
}>;
27+
}) {
28+
const params = await props.params;
29+
const values = await deserialize(productFlags, params.code);
30+
31+
const showFreeDeliveryBanner = await showFreeDeliveryBannerFlag(
32+
params.code,
33+
productFlags,
34+
);
35+
36+
const encryptedFlagValues = await encryptFlagValues(values);
37+
38+
return (
39+
<div className="bg-white">
40+
<FreeDelivery show={showFreeDeliveryBanner} />
41+
<Navigation />
42+
{props.children}
43+
<FlagValues values={encryptedFlagValues} />
44+
<Footer />
45+
<DevTools />
46+
</div>
47+
);
48+
}

app/[code]/page.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { SummerSale } from '@/app/summer-sale';
2+
import { ImageGallery } from '@/components/image-gallery';
3+
import { ProductDetails } from '@/components/product-detail-page/product-details';
4+
import { ProductHeader } from '@/components/product-detail-page/product-header';
5+
import { AddToCart } from '@/app/[code]/add-to-cart';
6+
import { ColorPicker } from '@/components/product-detail-page/color-picker';
7+
import { SizePicker } from '@/components/product-detail-page/size-picker';
8+
import { ProductDetailPageProvider } from '@/components/utils/product-detail-page-context';
9+
10+
import { productFlags, showSummerBannerFlag } from '@/flags';
11+
import { Main } from '@/components/main';
12+
13+
export default async function Page(props: {
14+
params: Promise<{ code: string }>;
15+
}) {
16+
const params = await props.params;
17+
18+
const showSummerBanner = await showSummerBannerFlag(
19+
params.code,
20+
productFlags,
21+
);
22+
23+
return (
24+
<ProductDetailPageProvider>
25+
<SummerSale show={showSummerBanner} />
26+
<Main>
27+
<div className="lg:grid lg:auto-rows-min lg:grid-cols-12 lg:gap-x-8">
28+
<ProductHeader />
29+
<ImageGallery />
30+
31+
<div className="mt-8 lg:col-span-5">
32+
<ColorPicker />
33+
<SizePicker />
34+
<AddToCart />
35+
<ProductDetails />
36+
</div>
37+
</div>
38+
</Main>
39+
</ProductDetailPageProvider>
40+
);
41+
}

0 commit comments

Comments
 (0)