diff --git a/nextjs-approuter/package-lock.json b/nextjs-approuter/package-lock.json index c759ac9..c14dbcd 100644 --- a/nextjs-approuter/package-lock.json +++ b/nextjs-approuter/package-lock.json @@ -11,7 +11,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.22.1", "js-cookie": "^3.0.5", "next": "14.1.0", "prettier": "^2.5.1", @@ -1844,9 +1844,9 @@ } }, "node_modules/flagsmith": { - "version": "3.23.1-beta.1", - "resolved": "https://registry.npmjs.org/flagsmith/-/flagsmith-3.23.1-beta.1.tgz", - "integrity": "sha512-26JlC62HkBWFs0rAZ2pqp8ipg80ELkrApN+wwX62ZtHW3vQ7LHI29UYsRrbvO1o65qHTKzrOIYEPx60aB3rwhg==" + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/flagsmith/-/flagsmith-3.22.1.tgz", + "integrity": "sha512-4FBUEnHae4BZ0fUKw6FKDzF6mxJfOHCAx/YiLc+hsDD3LBcfG0E0tV/Hgd2GGEJS7VoYwbt3MzCD04ReU+JNpw==" }, "node_modules/flat-cache": { "version": "3.2.0", diff --git a/nextjs-approuter/package.json b/nextjs-approuter/package.json index cc58fc4..ffa709d 100644 --- a/nextjs-approuter/package.json +++ b/nextjs-approuter/package.json @@ -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", diff --git a/nextjs/pages/[identity].tsx b/nextjs/pages/[identity].tsx index 4222a16..1e8a482 100644 --- a/nextjs/pages/[identity].tsx +++ b/nextjs/pages/[identity].tsx @@ -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 (
{ JSON.stringify(flags) } + Home diff --git a/nextjs/pages/_app.tsx b/nextjs/pages/_app.tsx index 727daf1..244a216 100644 --- a/nextjs/pages/_app.tsx +++ b/nextjs/pages/_app.tsx @@ -13,6 +13,7 @@ function MyApp({ Component, identity, pageProps, flagsmithState }: AppProps & {f return ( = ({ + serverState, + children, +}) => { + const flagsmithInstance = useRef(createFlagsmithInstance()) + return ( + + <>{children} + + ) +} + +export default FeatureFlagProvider diff --git a/split-test/app/components/Logo.tsx b/split-test/app/components/Logo.tsx new file mode 100644 index 0000000..d0fa4f2 --- /dev/null +++ b/split-test/app/components/Logo.tsx @@ -0,0 +1,65 @@ +import { FC } from 'react' + +type LogoType = {} + +const Logo: FC = ({}) => { + return ( + + + + + + + + + + + + + + ) +} + +export default Logo diff --git a/split-test/app/components/Nav.tsx b/split-test/app/components/Nav.tsx new file mode 100644 index 0000000..38888a5 --- /dev/null +++ b/split-test/app/components/Nav.tsx @@ -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) => { + const { name, value } = e.target + setFormData({ + ...formData, + [name]: value, + }) + } + + const handleLogin = (e: FormEvent) => { + e.preventDefault() + if (!disableLogin) { + login(formData) + } + } + + const handleLogout = () => { + logout() + } + + return ( +
+ {user ? ( + + ) : ( +
+ + + +
+ )} +
+ ) +} + +export default LoginForm diff --git a/split-test/app/components/WelcomeMessage.tsx b/split-test/app/components/WelcomeMessage.tsx new file mode 100644 index 0000000..89f0214 --- /dev/null +++ b/split-test/app/components/WelcomeMessage.tsx @@ -0,0 +1,49 @@ +import { FC, useState } from 'react' +import { useFlags, useFlagsmith, useFlagsmithLoading } from 'flagsmith/react' +import useUser from '@/app/hooks/useUser' + +type WelcomeMessageType = {} + +const WelcomeMessage: FC = ({}) => { + const { hero } = useFlags(['hero']) + useFlagsmithLoading() + const flagsmith = useFlagsmith() + const [lastTracked, setLastTracked] = useState(0) + + return ( +
+
+ {hero.value} hero + {!!flagsmith.identity && ( +
Tracked conversion events: {lastTracked}
+ )} +
+ {!!flagsmith.identity && ( +
+ + +
+ )} +
+ ) +} + +export default WelcomeMessage diff --git a/split-test/app/favicon.ico b/split-test/app/favicon.ico new file mode 100644 index 0000000..718d6fe Binary files /dev/null and b/split-test/app/favicon.ico differ diff --git a/split-test/app/hooks/useDefaultUser.ts b/split-test/app/hooks/useDefaultUser.ts new file mode 100644 index 0000000..7a15698 --- /dev/null +++ b/split-test/app/hooks/useDefaultUser.ts @@ -0,0 +1,14 @@ +import { cookies } from 'next/headers' +import { User } from '@/app/types' + +const useDefaultUser = () => { + const user = cookies().get('user')?.value + + try { + return user ? (JSON.parse(user) as User) : undefined + } catch (e) { + return undefined + } +} + +export default useDefaultUser diff --git a/split-test/app/hooks/useUser.ts b/split-test/app/hooks/useUser.ts new file mode 100644 index 0000000..1cfa35d --- /dev/null +++ b/split-test/app/hooks/useUser.ts @@ -0,0 +1,40 @@ +import { useCallback, useState } from 'react' +import { User } from '@/app/types' +import { useFlagsmith } from 'flagsmith/react' +import getTraits from '@/app/utils/getTraits' + +export interface LoginRequest { + email: string + password: string +} +export default function (defaultUser: User | null = null) { + const [user, setUser] = useState(defaultUser) + const flagsmith = useFlagsmith() + const login = useCallback((data: LoginRequest) => { + return fetch('/api/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }) + .then((res) => res.json()) + .then((res: User | null) => { + if (res) { + flagsmith.identify(res.id, getTraits(res)) + setUser(res) + } + }) + }, []) + + const logout = useCallback(() => { + setUser(null) + flagsmith.logout() + return fetch('/api/logout', { + method: 'POST', + body: '{}', + }) + }, []) + + return { login, logout, user } +} diff --git a/split-test/app/layout.tsx b/split-test/app/layout.tsx new file mode 100644 index 0000000..d3011fe --- /dev/null +++ b/split-test/app/layout.tsx @@ -0,0 +1,39 @@ +import type { Metadata } from 'next' +import { Inter } from 'next/font/google' +import './styles/index.scss' +import Nav from '@/app/components/Nav' +import useDefaultUser from '@/app/hooks/useDefaultUser' +import { createFlagsmithInstance } from 'flagsmith/isomorphic' +import FeatureFlagProvider from '@/app/components/FeatureFlagProvider' +import getTraits from '@/app/utils/getTraits' +const inter = Inter({ subsets: ['latin'] }) + +export const metadata: Metadata = { + title: 'Flagsmith with Next.js', + description: 'Generated by create next app', +} +export default async function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode +}>) { + const defaultUser = useDefaultUser() + const flagsmith = createFlagsmithInstance() + await flagsmith.init({ + environmentID: 'GjAAsnDhzDRoUCmji3p8Sh', + identity: defaultUser?.id, + api: 'http://localhost:8000/api/v1/', + traits: getTraits(defaultUser), + }) + const serverState = flagsmith.getState() + return ( + + + +