Files
Dominik Ferber a5bfb13c43 extended example (#84)
* wip

* adapt nav

* store management

* ssr cart

* clean up

* fix stableId issue

* allow image sorting

* toast on ProceedToCheckoutButton

* move actions, extract components

* Add PPR to Cart (#85)

* toast on Shop now

* allow delay

* loading spinner

* wip

* fix animations

* show cart count

* Extend shirt-shop demo with dynamic flag usage (#86)

* better metadata

* wip

* move cart to api

* wip

* respect free shipping

* add example banner

* init shadcn

* reorg

* remove relative paths from components

* do not link footer

* wip

* remove shadcn setup

* api-server » shirt-shop-api

* /api/:cartId » /api/cart/:cartId

* add delayFlag desc

* fix cart count

* remove quantity

* fine tune animation

* shrink

* list envs, fix Link

* change flag names

* set cart-id from middleware

* append headers

* allow cloning

* add BACKEND_URL fallback
2025-03-04 17:05:13 +01:00

81 lines
2.4 KiB
TypeScript

import { flag } from 'flags/next';
import { identify, type EvaluationContext } from './lib/identify';
import { xxHash32 } from 'js-xxhash';
/**
* Takes a string and puts it into a bucket.
*
* Typically the key consists of the experiment name and the stableId, such that
* every experiment has a different outcome. If it would depend on the stableId only,
* then a user would consistently get assigned the same bucket for all experiments,
* which we need to avoid.
*
* @param key - The key to hash.
* @param buckets - The number of buckets to use.
* @returns The determined bucket number.
*/
function bucket(key: string, buckets: number = 2) {
const hashNum = xxHash32(key);
return hashNum % buckets;
}
export const showSummerBannerFlag = flag<boolean, EvaluationContext>({
key: 'summer-sale',
description: 'Shows a bright yellow banner for a 20% discount',
defaultValue: false,
identify,
decide({ entities }) {
if (!entities || !entities.stableId) return this.defaultValue!;
return bucket(`${this.key}/${entities.stableId}`) === 1;
},
});
export const showFreeDeliveryBannerFlag = flag<boolean, EvaluationContext>({
key: 'free-delivery',
description: 'Show a black free delivery banner at the top of the page',
defaultValue: false,
identify,
decide({ entities }) {
if (!entities || !entities.stableId) return this.defaultValue!;
return bucket(`${this.key}/${entities.stableId}`) === 1;
},
});
export const proceedToCheckoutColorFlag = flag<string, EvaluationContext>({
key: 'proceed-to-checkout-color',
description: 'The color of the proceed to checkout button',
defaultValue: 'blue',
options: ['blue', 'green', 'red'],
identify,
decide({ entities }) {
if (!entities || !entities.stableId) {
return this.defaultValue as string;
}
return this.options![
bucket(`${this.key}/${entities.stableId}`, this.options!.length)
] as string;
},
});
export const delayFlag = flag<number>({
key: 'delay',
defaultValue: 0,
description:
'A flag for debugging and demo purposes which delays the data loading',
options: [
{ value: 0, label: 'No delay' },
{ value: 200, label: '200ms' },
{ value: 1000, label: '1s' },
{ value: 3000, label: '3s' },
{ value: 10_000, label: '10s' },
],
decide() {
return this.defaultValue!;
},
});
export const productFlags = [
showFreeDeliveryBannerFlag,
showSummerBannerFlag,
] as const;