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 ( -
+
{imageData && (
{ @@ -9,14 +11,14 @@ const ProductSection = ({ data }: { data: Queries.ProductSectionFragment }) => { } return ( -
+

{data.title}

{data.description?.description && (
{data.description.description}
)} -
+
{data.products.map((product) => { if (!product) { return null; @@ -35,13 +37,7 @@ const ProductSection = ({ data }: { data: Queries.ProductSectionFragment }) => { imageData={image?.gatsbyImageData || undefined} footer={Catch it!} > -
+
{shortDescription?.shortDescription && (

{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 ( + <> +
+ {data.a.map((section) => { + if (!section) { + return null; + } + + const Component = + typenameToSectionComponentMapExperiment[section.__typename]; + + if (!Component) { + return null; + } + + return ( +
+ {/* @ts-ignore No idea why an error here either. */} + +
+ ); + })} +
+
+ {data.b.map((section) => { + if (!section) { + return null; + } + + const Component = + typenameToSectionComponentMapExperiment[section.__typename]; + + if (!Component) { + return null; + } + + return ( +
+ {/* @ts-ignore No idea why an error here either. */} + +
+ ); + })} +
+ + ); +}; + +const typenameToSectionComponentMap = { + ContentfulExperimentReferences: ExperimentSection, +}; + // @todo: this should be changed to Sections fragment. Not sure why isn't working yet. const Sections = ({ data }: { data: readonly Queries.SectionsFragment[] }) => { return ( @@ -34,9 +91,15 @@ export default Sections; export const query = graphql` # Fragment type should be changed to custom union. - fragment Sections on ContentfulProductSectionContentfulTitleWithContentUnion { + fragment Sections on ContentfulExperimentReferences { __typename - ...ProductSection - ...TitleWithContent + a { + ...ProductSection + ...TitleWithContent + } + b { + ...ProductSection + ...TitleWithContent + } } `; diff --git a/src/templates/page.tsx b/src/templates/page.tsx index 48f1712..73ea1f4 100644 --- a/src/templates/page.tsx +++ b/src/templates/page.tsx @@ -27,7 +27,17 @@ const IndexPage = ({ data }: PageProps) => { export default IndexPage; -export const Head: HeadFC = () => Home Page; +export const Head: HeadFC = ({ data }) => ( + <> + Home Page + {/* @ts-ignore */} + + +); export const query = graphql` query Page($slug: String!) { @@ -40,5 +50,19 @@ export const query = graphql` ...Sections } } + allContentfulCampaign { + nodes { + id + trafficAmount + experiments { + ... on ContentfulExperiment { + slug + } + ... on ContentfulExperimentReferences { + slug + } + } + } + } } `; diff --git a/yarn.lock b/yarn.lock index 5dd51b4..d2a6d76 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3758,7 +3758,7 @@ ci-info@2.0.0, ci-info@^2.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== -classnames@^2.3.1: +classnames@^2.3.1, classnames@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==