Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore/add split test example #11

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
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
8 changes: 4 additions & 4 deletions nextjs-approuter/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion nextjs-approuter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"bootstrap": "^5.3.2",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"flagsmith": "^3.23.1-beta.1",
"flagsmith": "^3.23.0",
"js-cookie": "^3.0.5",
"next": "14.1.0",
"prettier": "^2.5.1",
Expand Down
14 changes: 13 additions & 1 deletion nextjs/pages/[identity].tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
import type { NextPage } from 'next';
import { useFlags } from 'flagsmith-es/react';
import {useFlags, useFlagsmith} from 'flagsmith-es/react';
import Link from "next/link";

const Home: NextPage = () => {
const flags = useFlags(["font_size"]) // only causes re-render if specified flag values / traits change
console.log("Rendering", flags.font_size.value)
const flagsmith = useFlagsmith()

return (
<div className="App">
{
JSON.stringify(flags)
}
<button onClick={()=>{
flagsmith.setContext({
identity: {
traits: {
a: 1,
b: 2,
}
}
})
}}>Trait</button>
<Link href="/">
Home
</Link>
Expand Down
1 change: 1 addition & 0 deletions nextjs/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ function MyApp({ Component, identity, pageProps, flagsmithState }: AppProps & {f
return (
<FlagsmithProvider flagsmith={flagsmithRef.current} options={{
environmentID,
enableAnalytics: true,
preventFetch: isClient, // optionally prevent clientside fetching of flags
state: flagsmithState
}} key={identity}
Expand Down
6 changes: 6 additions & 0 deletions split-test/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": [
"next/core-web-vitals",
"plugin:prettier/recommended"
]
}
36 changes: 36 additions & 0 deletions split-test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 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
.idea
# 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
1 change: 1 addition & 0 deletions split-test/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v18.17.0
16 changes: 16 additions & 0 deletions split-test/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"arrowParens": "always",
"bracketSpacing": true,
"insertPragma": false,
"jsxSingleQuote": true,
"printWidth": 80,
"proseWrap": "preserve",
"quoteProps": "preserve",
"requirePragma": false,
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"useTabs": false,
"vueIndentScriptAndStyle": false
}
22 changes: 22 additions & 0 deletions split-test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[![Feature Flag, Remote Config and A/B Testing platform, Flagsmith](https://github.com/Flagsmith/flagsmith/raw/main/static-files/hero.png)](https://www.flagsmith.com/)

## Next.js and Flagsmith Tutorial

The repository for the [live stream tutorial](https://www.youtube.com/watch?v=u9TjbtZX4Zg) with [@eddiejaoude](https://twitter.com/eddiejaoude).

## Getting started


**Install**

```
npm i
```

**Run**
```
npm run dev
```



19 changes: 19 additions & 0 deletions split-test/app/api/login/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use server'

import { cookies } from 'next/headers'
import { LoginRequest } from '@/app/types'
import { NextResponse } from 'next/server'

export async function POST(request: Request) {
const body: LoginRequest = await request.json()
//Mock a user id by formatting the email address
const id = body.email.split('@').join('_').replace(/\./, '_')
// Create a mock user object and store it in cookie
const userJSON = {
id,
email: body.email,
}
const user = JSON.stringify(userJSON)
cookies().set('user', user)
return NextResponse.json(userJSON)
}
11 changes: 11 additions & 0 deletions split-test/app/api/logout/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use server'

import { cookies } from 'next/headers'

export async function POST() {
// Mock login API
cookies().delete('user')
return new Response('{}', {
status: 200,
})
}
32 changes: 32 additions & 0 deletions split-test/app/components/FeatureFlagProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use client'
import { FC, ReactNode, useRef } from 'react'
import { IState } from 'flagsmith/types'
import { FlagsmithProvider } from 'flagsmith/react'
import { createFlagsmithInstance } from 'flagsmith/isomorphic'

type FeatureFlagProviderType = {
serverState: IState
children: ReactNode
}

const FeatureFlagProvider: FC<FeatureFlagProviderType> = ({
serverState,
children,
}) => {
const flagsmithInstance = useRef(createFlagsmithInstance())
return (
<FlagsmithProvider
options={{
enableAnalytics: true,
splitTestingAnalytics: true,
environmentID: serverState.environmentID,
}}
flagsmith={flagsmithInstance.current}
serverState={serverState}
>
<>{children}</>
</FlagsmithProvider>
)
}

export default FeatureFlagProvider
65 changes: 65 additions & 0 deletions split-test/app/components/Logo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { FC } from 'react'

type LogoType = {}

const Logo: FC<LogoType> = ({}) => {
return (
<svg
xmlns='http://www.w3.org/2000/svg'
height='42'
viewBox='0 0 142 30'
fill='none'
>
<path
d='M109.697 23.3101V7.83004H104.851V23.3101H109.697Z'
fill='#6837FC'
/>
<path
d='M105.345 4.63503C105.873 5.16528 106.517 5.4301 107.274 5.4301C108.052 5.4301 108.705 5.16528 109.234 4.63503C109.762 4.10519 110.026 3.47008 110.026 2.7301C110.026 1.99034 109.751 1.3499 109.204 0.810017C108.655 0.269939 108.011 0 107.274 0C106.536 0 105.898 0.269939 105.36 0.810017C104.822 1.3499 104.552 1.99034 104.552 2.7301C104.552 3.47008 104.817 4.10519 105.345 4.63503Z'
fill='#6837FC'
/>
<path
d='M61.0036 21.7202L63.1273 18.48C64.2236 19.0605 65.1412 19.4654 65.879 19.6951C66.6165 19.9253 67.3545 20.04 68.0925 20.04C68.7699 20.04 69.3087 19.9253 69.7075 19.6951C70.1064 19.4654 70.3061 19.1603 70.3061 18.7801C70.3061 18.48 70.1906 18.2401 69.9616 18.0601C69.7327 17.8801 69.3685 17.7503 68.8698 17.6701L65.909 17.13C64.4135 16.8502 63.2816 16.3304 62.5142 15.5701C61.7463 14.8103 61.3626 13.83 61.3626 12.6301C61.3626 11.0503 61.9355 9.81004 63.0825 8.91002C64.2289 8.01008 65.809 7.55994 67.8231 7.55994C68.9004 7.55994 69.9664 7.71007 71.0236 8.01008C72.0801 8.30995 73.1074 8.75027 74.1048 9.3301L71.8912 12.4201C71.1133 11.9804 70.3605 11.6504 69.6328 11.4301C68.9044 11.2102 68.1822 11.1001 67.4644 11.1001C66.8857 11.1001 66.4324 11.1952 66.1034 11.385C65.7745 11.5754 65.6099 11.8402 65.6099 12.1801C65.6099 12.5001 65.7244 12.7402 65.954 12.9001C66.1829 13.0604 66.5968 13.1902 67.1953 13.29L70.0962 13.83C71.6113 14.1103 72.7684 14.6354 73.566 15.4051C74.3637 16.1752 74.7523 17.1404 74.7326 18.3001C74.7326 19.0605 74.5682 19.7653 74.2393 20.4151C73.9098 21.0652 73.4464 21.6254 72.8485 22.0951C72.2499 22.5652 71.542 22.9351 70.7246 23.2051C69.9066 23.4753 69.0295 23.6102 68.0925 23.6102C66.6964 23.6102 65.3902 23.4499 64.1741 23.1301C62.9577 22.8104 61.9008 22.3404 61.0036 21.7202Z'
fill='#6837FC'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M27.3172 22.245C28.3139 23.115 29.5903 23.55 31.1457 23.55C31.9631 23.55 32.7109 23.4247 33.3888 23.175C34.0665 22.9252 34.7447 22.5201 35.4228 21.9599V23.31H40.2084V13.4099C40.2084 11.5701 39.615 10.1399 38.4288 9.11985C37.2423 8.0999 35.5823 7.58997 33.4487 7.58997C32.3123 7.58997 31.1606 7.73001 29.994 8.00997C28.8277 8.2902 27.6162 8.71021 26.36 9.26999L28.035 12.66C28.9322 12.2803 29.7347 12.0051 30.4429 11.8349C31.1504 11.6652 31.7934 11.5799 32.3721 11.5799C33.3888 11.5799 34.1517 11.7703 34.6602 12.15C35.1686 12.5301 35.4228 13.0903 35.4228 13.8299V15.09C34.7447 14.8302 34.1166 14.6452 33.5385 14.5349C32.9598 14.4252 32.3915 14.37 31.8337 14.37C29.9389 14.37 28.4636 14.7652 27.4068 15.5549C26.3497 16.3453 25.8215 17.4501 25.8215 18.8699C25.8215 20.25 26.3198 21.375 27.3172 22.245ZM30.9064 19.935C30.4876 19.665 30.2783 19.2802 30.2783 18.78C30.2783 18.2803 30.4923 17.9001 30.9213 17.64C31.3499 17.3803 31.9631 17.25 32.7609 17.25C33.1991 17.25 33.6433 17.28 34.0919 17.3399C34.5405 17.4 34.984 17.4801 35.4228 17.58V19.5599C35.0042 19.82 34.5601 20.015 34.0919 20.145C33.623 20.2753 33.1393 20.34 32.6412 20.34C31.9033 20.34 31.3252 20.205 30.9064 19.935Z'
fill='#6837FC'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M46.7935 22.665C47.6311 23.0752 48.5181 23.2801 49.4556 23.2801C50.3528 23.2801 51.2003 23.0955 51.9979 22.725C52.7952 22.3551 53.4433 21.8603 53.942 21.2401V23.4601C53.942 24.3999 53.643 25.1099 53.0448 25.5901C52.4467 26.0702 51.5793 26.31 50.4427 26.31C49.7047 26.31 48.9471 26.2099 48.1693 26.0102C47.3918 25.8098 46.574 25.51 45.7167 25.1099L44.2212 28.6201C45.1383 29.0597 46.1453 29.4001 47.2421 29.6401C48.3387 29.8801 49.4654 30 50.6221 30C53.2341 30 55.2431 29.4401 56.649 28.3202C58.0548 27.1999 58.7578 25.5999 58.7578 23.5201V7.83006H54.002V9.69002C53.4832 9.07037 52.8602 8.5903 52.1326 8.25013C51.4044 7.91038 50.6318 7.73999 49.8146 7.73999C48.8172 7.73999 47.8802 7.94024 47.003 8.34007C46.1253 8.74043 45.3625 9.29019 44.7147 9.99002C44.0666 10.6904 43.558 11.5102 43.1894 12.4501C42.8202 13.3904 42.636 14.4001 42.636 15.4801C42.636 16.56 42.8155 17.5754 43.1743 18.5251C43.5333 19.4753 44.0166 20.3001 44.6249 21.0001C45.233 21.7005 45.956 22.2553 46.7935 22.665ZM48.4536 18.2251C47.7456 17.4952 47.3918 16.59 47.3918 15.5101C47.3918 14.4301 47.7403 13.53 48.4387 12.8101C49.1364 12.09 50.0136 11.73 51.0708 11.73C51.6289 11.73 52.1522 11.835 52.6411 12.045C53.1295 12.255 53.5632 12.5504 53.942 12.9301V18.0901C53.5632 18.4904 53.1346 18.7951 52.6559 19.0051C52.1776 19.2151 51.6489 19.32 51.0708 19.32C50.0337 19.32 49.1611 18.9553 48.4536 18.2251Z'
fill='#6837FC'
/>
<path
d='M115.5 18.54V11.5799H112.509V7.83002H115.5V4.15452L120.554 3.15198L120.345 7.83002H124.473V11.5799H120.345V17.58C120.345 18.2601 120.5 18.7402 120.809 19.02C121.118 19.3003 121.641 19.44 122.38 19.44C122.658 19.44 122.937 19.4203 123.217 19.38C123.496 19.3402 123.875 19.2502 124.353 19.11V23.0402C123.914 23.1802 123.376 23.295 122.738 23.385C122.1 23.4752 121.532 23.52 121.033 23.52C119.219 23.52 117.842 23.1 116.905 22.26C115.968 21.4199 115.5 20.1801 115.5 18.54Z'
fill='#6837FC'
/>
<path
d='M127.164 1.65523V23.3101H132.01V13.7601C131.944 12.8777 132.211 12.3427 133.011 11.8076C133.408 11.5423 134.063 11.4 134.582 11.4C135.36 11.4 135.983 11.6652 136.452 12.1949C136.92 12.7251 137.155 13.4204 137.155 14.28V23.3101H142V12.87C142 11.3301 141.521 10.065 140.564 9.07502C139.607 8.085 138.38 7.59 136.885 7.59C135.888 7.59 134.976 7.8253 134.148 8.29494C133.321 8.7652 132.608 9.44982 132.01 10.35V0.495605L127.164 1.65523Z'
fill='#6837FC'
/>
<path
d='M77.2116 23.415H82.3305C82.4419 19.5324 82.6213 13.3262 82.783 12.6817C83.0656 11.552 83.8768 11.0949 84.987 11.1921C85.9838 11.2793 86.5715 11.8633 86.7461 13.0125C86.8279 13.5519 86.8449 14.1032 86.8616 14.6502L86.8623 14.6705C86.8733 15.0432 86.7765 19.3753 86.6974 22.9187L86.6973 22.9222L86.6863 23.415H91.7093C91.7759 19.6524 91.8663 15.276 91.9138 14.6783C91.9757 13.9041 92.1095 13.0978 92.4098 12.3905C92.7889 11.4961 93.698 11.149 94.6574 11.315C95.4754 11.4565 95.9483 11.94 96.1161 12.9596C96.2268 13.6333 96.2527 14.3265 96.2595 15.0114C96.269 15.9572 96.2017 19.7012 96.1338 23.415H101.096C101.178 18.4756 101.257 13.1854 101.2 11.2809C101.152 9.70175 100.247 8.51087 98.7489 7.85897C96.1222 6.71552 93.2149 7.4524 91.4498 9.70672C91.4214 9.74307 91.3765 9.76628 91.3247 9.79308C91.2984 9.80665 91.2704 9.82114 91.2419 9.83872C90.3083 8.26493 88.8883 7.52678 87.1238 7.39362C85.3594 7.26033 83.8524 7.88372 82.5302 9.16583C82.4996 8.568 82.4772 8.12829 82.4521 7.62838C82.0145 7.62838 81.5825 7.62927 81.1544 7.63015C80.0746 7.63237 79.0204 7.63454 77.9665 7.62224C77.4526 7.61634 77.4533 7.92864 77.4542 8.2844L77.4542 8.30236C77.4569 9.86123 77.338 19.6659 77.2116 23.415Z'
fill='#6837FC'
/>
<path
d='M18.5496 23.4105V8.93828C21.5153 8.09056 22.6919 5.622 23.3951 3.64478V23.4105H18.5496Z'
fill='#6837FC'
/>
<path
d='M0.530648 10.326C-0.531097 6.77224 -0.000742912 3.37303 4.50803 2.08663C5.65146 1.76037 7.39441 1.52463 8.57757 1.5062C12.8964 1.43882 21.8399 1.49207 22.0443 1.50825C22.1566 1.51725 21.7243 3.22373 20.967 4.35222C19.7282 6.19796 17.9634 7.06411 15.7338 7.17675C12.9884 7.31557 10.2352 7.34141 7.48532 7.34264C4.76279 7.34407 2.42682 8.16083 0.632348 10.2911C0.618637 10.3075 0.591743 10.3128 0.564974 10.318C0.553132 10.3204 0.541288 10.3227 0.530648 10.326Z'
fill='#6837FC'
/>
<path
d='M6.82826 9.47776L14.1115 9.49106V14.3803H6.82826V23.2958C3.05154 23.2629 0 20.1825 0 16.3868C0 12.591 3.05154 9.51076 6.82826 9.47776Z'
fill='#6837FC'
/>
</svg>
)
}

export default Logo
74 changes: 74 additions & 0 deletions split-test/app/components/Nav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
'use client'

import React, { ChangeEvent, FC, FormEvent, useState } from 'react'
import { User } from '@/app/types'
import useUser from '@/app/hooks/useUser'
const LoginForm: FC<{ defaultUser: User | undefined }> = ({ defaultUser }) => {
const { login, logout, user } = useUser(defaultUser)
const [formData, setFormData] = useState({
email: '',
password: 'example',
})

const disableLogin = !formData.email || !formData.password

const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target
setFormData({
...formData,
[name]: value,
})
}

const handleLogin = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
if (!disableLogin) {
login(formData)
}
}

const handleLogout = () => {
logout()
}

return (
<div className='d-flex p-4 flex-row justify-content-end'>
{user ? (
<button onClick={handleLogout} className='btn btn-link'>
Logout
</button>
) : (
<form className='d-flex flex-row gap-4' onSubmit={handleLogin}>
<input
className='form-control'
id='email'
name='email'
placeholder='Username'
value={formData.email}
onChange={handleChange}
required
/>
<input
placeholder='Password'
type='password'
className='form-control'
id='password'
name='password'
value={formData.password}
onChange={handleChange}
required
/>
<button
disabled={disableLogin}
type='submit'
className='btn btn-primary'
>
Login
</button>
</form>
)}
</div>
)
}

export default LoginForm
Loading