diff --git a/.gitignore b/.gitignore index 0c5275e..3ca5dfc 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ public src/gatsby-types.d.ts .env.* !.env.example +.netlify diff --git a/.vscode/settings.json b/.vscode/settings.json index 6595c3a..9382ed9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,6 @@ "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.organizeImports.rome": true - } + }, + "deno.enablePaths": ["netlify/edge-functions"] } diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 0000000..73129e8 --- /dev/null +++ b/netlify.toml @@ -0,0 +1,2 @@ +[build] +publish="public" diff --git a/netlify/edge-functions/experiment.ts b/netlify/edge-functions/experiment.ts new file mode 100644 index 0000000..ae5c335 --- /dev/null +++ b/netlify/edge-functions/experiment.ts @@ -0,0 +1,80 @@ +import { Context } from "https://edge.netlify.com"; +import { HTMLRewriter } from "https://raw.githubusercontent.com/worker-tools/html-rewriter/master/index.ts"; + +type Experiment = { + slug: string; +}; + +type CampaignsConfig = { + campaigns: { id: string; trafficAmount: number; experiments: Experiment[] }[]; +}; +export default async (request: Request, context: Context) => { + const next = await context.next(); + let config: CampaignsConfig = { campaigns: [] }; + let buffer = ""; + + Array(10) + .fill() + .map((_, index) => { + const name = `edge-campaign-${index}`; + console.log({ name }); + const isSet = context.cookies.get(name); + + console.log({ index, isSet }); + + if (!isSet) { + context.cookies.set({ + name, + value: Math.random(), + }); + } + }); + + return new HTMLRewriter() + .on("#edge-config", { + text(scriptText) { + buffer += scriptText.text; + if (scriptText.lastInTextNode) { + config = JSON.parse(buffer); + } + }, + }) + .on("body", { + element(bodyEl) { + try { + const experimentSlugs = config?.campaigns + ?.filter(({ id, trafficAmount }, index) => { + const name = `edge-campaign-${index}`; + const probability = parseFloat(context.cookies.get(name)); + + console.log({ probability }); + + const isOn = probability > trafficAmount / 100; + + console.log("BUCKET", isOn); + + return isOn; + }) + .map((campaign) => { + return campaign.experiments.map( + ({ slug }) => `edge-experiment-${slug}` + ); + }) + .flat() + .join(" "); + + console.log({ experimentSlugs }); + + bodyEl.setAttribute("class", experimentSlugs); + + console.log("EDGE CONFIG", JSON.stringify(config, null, 2)); + } catch (error) { + console.log("ERROR"); + console.log(error); + } + }, + }) + .transform(next); +}; + +export const config = { path: "/" }; diff --git a/package.json b/package.json index 04977b1..000f6fc 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@commercelayer/js-auth": "4.0.0", "@commercelayer/react-components": "^4.4.4", "@frontegg/react": "^5.0.44", + "classnames": "^2.3.2", "gatsby": "^5.9.0", "gatsby-plugin-image": "^3.11.0", "gatsby-plugin-manifest": "^5.9.0", diff --git a/src/components/Card/src/Card.module.scss b/src/components/Card/src/Card.module.scss index b0de844..09c934d 100644 --- a/src/components/Card/src/Card.module.scss +++ b/src/components/Card/src/Card.module.scss @@ -30,3 +30,15 @@ .footer { padding: 20px; } + +:global(.edge-experiment-list-no-cards .product-section) { + .card { + flex-direction: row; + justify-content: space-between; + } + + .image, + .imageWrapper { + display: none; + } +} diff --git a/src/components/Card/src/Card.tsx b/src/components/Card/src/Card.tsx index bcac20f..61df75a 100644 --- a/src/components/Card/src/Card.tsx +++ b/src/components/Card/src/Card.tsx @@ -1,5 +1,6 @@ import { GatsbyImage, IGatsbyImageData } from "gatsby-plugin-image"; import { cloneElement, isValidElement, useMemo } from "react"; +import classnames from "classnames"; import * as styles from "./Card.module.scss"; export type CardProps = { @@ -7,11 +8,12 @@ export type CardProps = { children: React.ReactNode; imageData?: IGatsbyImageData; footer?: React.ReactNode; + className?: string; }; -const Card = ({ title, children, imageData, footer }: CardProps) => { +const Card = ({ title, children, imageData, footer, className }: CardProps) => { return ( -
{shortDescription?.shortDescription}
)} diff --git a/src/components/Sections/src/Sections.module.scss b/src/components/Sections/src/Sections.module.scss index ea8f708..9f7093d 100644 --- a/src/components/Sections/src/Sections.module.scss +++ b/src/components/Sections/src/Sections.module.scss @@ -3,3 +3,17 @@ padding-top: 50px; border-top: 1px solid #ccc; } + +.b { + display: none; +} + +:global(.edge-experiment-invert-order-homepage) { + .a { + display: none; + } + + .b { + display: block; + } +} diff --git a/src/components/Sections/src/Sections.tsx b/src/components/Sections/src/Sections.tsx index 13cec25..6e5a978 100644 --- a/src/components/Sections/src/Sections.tsx +++ b/src/components/Sections/src/Sections.tsx @@ -3,11 +3,68 @@ import ProductSection from "../../ProductSection"; import TitleWithContent from "../../TitleWithContent"; import * as styles from "./Sections.module.scss"; -const typenameToSectionComponentMap = { +const typenameToSectionComponentMapExperiment = { ContentfulProductSection: ProductSection, ContentfulTitleWithContent: TitleWithContent, }; +const ExperimentSection = ({ data }: { data: Queries.SectionsFragment }) => { + if (!data.a || !data.b) { + return null; + } + + return ( + <> +