diff --git a/src/components/common/NetworkSelector/index.tsx b/src/components/common/NetworkSelector/index.tsx
index 0ca280bd10..fd9bdc0971 100644
--- a/src/components/common/NetworkSelector/index.tsx
+++ b/src/components/common/NetworkSelector/index.tsx
@@ -24,7 +24,9 @@ const NetworkSelector = (): ReactElement => {
trackEvent({ ...OVERVIEW_EVENTS.SWITCH_NETWORK, label: selectedChainId })
- const shouldKeepPath = [AppRoutes.load, AppRoutes.open].includes(router.pathname)
+ const shouldKeepPath = [AppRoutes.load, AppRoutes.open, AppRoutes.newSafe.create, AppRoutes.newSafe.add].includes(
+ router.pathname,
+ )
const newRoute = {
pathname: shouldKeepPath ? router.pathname : '/',
diff --git a/src/components/safe-apps/SafeAppLandingPage/AppActions.tsx b/src/components/safe-apps/SafeAppLandingPage/AppActions.tsx
index 27574d9e23..76bd42eb51 100644
--- a/src/components/safe-apps/SafeAppLandingPage/AppActions.tsx
+++ b/src/components/safe-apps/SafeAppLandingPage/AppActions.tsx
@@ -15,6 +15,7 @@ import { AppRoutes } from '@/config/routes'
import useOwnedSafes from '@/hooks/useOwnedSafes'
import { CTA_BUTTON_WIDTH, CTA_HEIGHT } from '@/components/safe-apps/SafeAppLandingPage/constants'
import CreateNewSafeSVG from '@/public/images/open/safe-creation.svg'
+import useNewSafeRoutes from '@/hooks/useNewSafeRoutes'
type Props = {
appUrl: string
@@ -30,6 +31,7 @@ const AppActions = ({ wallet, onConnectWallet, chain, appUrl, app }: Props): Rea
const lastUsedSafe = useLastSafe()
const ownedSafes = useOwnedSafes()
const addressBook = useAppSelector(selectAllAddressBooks)
+ const { createSafe } = useNewSafeRoutes()
const chains = useAppSelector(selectChains)
const compatibleChains = app.chainIds
@@ -71,7 +73,7 @@ const AppActions = ({ wallet, onConnectWallet, chain, appUrl, app }: Props): Rea
case shouldCreateSafe:
const redirect = `${AppRoutes.apps}?appUrl=${appUrl}`
const createSafeHrefWithRedirect: UrlObject = {
- pathname: AppRoutes.open,
+ pathname: createSafe,
query: { safeViewRedirectURL: redirect, chain: chain.shortName },
}
button = (
diff --git a/src/components/welcome/index.tsx b/src/components/welcome/index.tsx
index 89b2be5346..f1b445139d 100644
--- a/src/components/welcome/index.tsx
+++ b/src/components/welcome/index.tsx
@@ -3,9 +3,11 @@ import { Button, Divider, Grid, Paper, Typography } from '@mui/material'
import { useRouter } from 'next/router'
import { CREATE_SAFE_EVENTS, LOAD_SAFE_EVENTS } from '@/services/analytics/events/createLoadSafe'
import Track from '../common/Track'
+import useNewSafeRoutes from '@/hooks/useNewSafeRoutes'
const NewSafe = () => {
const router = useRouter()
+ const { createSafe, addSafe } = useNewSafeRoutes()
return (
<>
@@ -28,7 +30,7 @@ const NewSafe = () => {
for creating your new Safe.
@@ -43,7 +45,7 @@ const NewSafe = () => {
address.
diff --git a/src/config/routes.ts b/src/config/routes.ts
index 8696da8c5a..9f7b10bcd7 100644
--- a/src/config/routes.ts
+++ b/src/config/routes.ts
@@ -5,13 +5,16 @@ export const AppRoutes = {
load: '/load',
index: '/',
home: '/home',
- createSafe: '/create-safe',
apps: '/apps',
addressBook: '/address-book',
balances: {
nfts: '/balances/nfts',
index: '/balances',
},
+ newSafe: {
+ create: '/new-safe/create',
+ add: '/new-safe/add',
+ },
settings: {
spendingLimits: '/settings/spending-limits',
setup: '/settings/setup',
diff --git a/src/hooks/useABTest.ts b/src/hooks/useABTest.ts
new file mode 100644
index 0000000000..f1c2b4ad02
--- /dev/null
+++ b/src/hooks/useABTest.ts
@@ -0,0 +1,18 @@
+import { localItem } from '@/services/local-storage/local'
+import useLocalStorage from '@/services/local-storage/useLocalStorage'
+
+const getAbTestKey = (name: string) => {
+ return `AB__${name}`
+}
+
+export const getAbTestIsB = (name: string) => {
+ return localItem(getAbTestKey(name)).get()
+}
+
+const useABTest = (name: string): boolean => {
+ const [isB] = useLocalStorage(getAbTestKey(name), Math.random() > 0.5, true)
+
+ return isB
+}
+
+export default useABTest
diff --git a/src/hooks/useNewSafeRoutes.ts b/src/hooks/useNewSafeRoutes.ts
new file mode 100644
index 0000000000..6bf66dc024
--- /dev/null
+++ b/src/hooks/useNewSafeRoutes.ts
@@ -0,0 +1,15 @@
+import useABTest from '@/hooks/useABTest'
+import { AppRoutes } from '@/config/routes'
+
+export const NEW_SAFE_AB_TEST_NAME = 'newSafe'
+
+const useNewSafeRoutes = () => {
+ const shouldUseNewRoute = useABTest(NEW_SAFE_AB_TEST_NAME)
+
+ return {
+ createSafe: shouldUseNewRoute ? AppRoutes.newSafe.create : AppRoutes.open,
+ addSafe: shouldUseNewRoute ? AppRoutes.newSafe.add : AppRoutes.load,
+ }
+}
+
+export default useNewSafeRoutes
diff --git a/src/pages/demo.tsx b/src/pages/demo.tsx
deleted file mode 100644
index 20c09006d8..0000000000
--- a/src/pages/demo.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import type { NextPage } from 'next'
-import Head from 'next/head'
-import CreateSafe from '@/components/new-safe/CreateSafe'
-
-const Open: NextPage = () => {
- return (
-
-
- Safe – Create Safe
-
-
-
-
- )
-}
-
-export default Open
diff --git a/src/pages/new-safe/add.tsx b/src/pages/new-safe/add.tsx
new file mode 100644
index 0000000000..9355127e8a
--- /dev/null
+++ b/src/pages/new-safe/add.tsx
@@ -0,0 +1,3 @@
+import Load from '../load'
+
+export default
diff --git a/src/pages/create-safe.tsx b/src/pages/new-safe/create.tsx
similarity index 100%
rename from src/pages/create-safe.tsx
rename to src/pages/new-safe/create.tsx
diff --git a/src/services/analytics/events/createLoadSafe.ts b/src/services/analytics/events/createLoadSafe.ts
index e210008f20..dd443177f4 100644
--- a/src/services/analytics/events/createLoadSafe.ts
+++ b/src/services/analytics/events/createLoadSafe.ts
@@ -1,59 +1,77 @@
+import { getAbTestIsB } from '@/hooks/useABTest'
+import { NEW_SAFE_AB_TEST_NAME } from '@/hooks/useNewSafeRoutes'
import { EventType } from '@/services/analytics/types'
export const CREATE_SAFE_CATEGORY = 'create-safe'
+// We cannot read the value immediately as the option may not have been decided yet
+const isB = () => {
+ return getAbTestIsB(NEW_SAFE_AB_TEST_NAME) ? 'New' : 'Old'
+}
+
export const CREATE_SAFE_EVENTS = {
CREATE_BUTTON: {
action: 'Open stepper',
category: CREATE_SAFE_CATEGORY,
+ abTest: isB,
},
NAME_SAFE: {
event: EventType.META,
action: 'Name Safe',
category: CREATE_SAFE_CATEGORY,
+ abTest: isB,
},
OWNERS: {
event: EventType.META,
action: 'Owners',
category: CREATE_SAFE_CATEGORY,
+ abTest: isB,
},
THRESHOLD: {
event: EventType.META,
action: 'Threshold',
category: CREATE_SAFE_CATEGORY,
+ abTest: isB,
},
SUBMIT_CREATE_SAFE: {
event: EventType.META,
action: 'Submit Safe creation',
category: CREATE_SAFE_CATEGORY,
+ abTest: isB,
},
REJECT_CREATE_SAFE: {
event: EventType.META,
action: 'Reject Safe creation',
category: CREATE_SAFE_CATEGORY,
+ abTest: isB,
},
RETRY_CREATE_SAFE: {
event: EventType.META,
action: 'Retry Safe creation',
category: CREATE_SAFE_CATEGORY,
+ abTest: isB,
},
CANCEL_CREATE_SAFE: {
event: EventType.META,
action: 'Cancel Safe creation',
category: CREATE_SAFE_CATEGORY,
+ abTest: isB,
},
CREATED_SAFE: {
event: EventType.META,
action: 'Created Safe',
category: CREATE_SAFE_CATEGORY,
+ abTest: isB,
},
GET_STARTED: {
action: 'Load Safe',
category: CREATE_SAFE_CATEGORY,
+ abTest: isB,
},
GO_TO_SAFE: {
action: 'Open Safe',
category: CREATE_SAFE_CATEGORY,
+ abTest: isB,
},
}
diff --git a/src/services/analytics/gtm.ts b/src/services/analytics/gtm.ts
index 6520a1b9ed..ed56ccd556 100644
--- a/src/services/analytics/gtm.ts
+++ b/src/services/analytics/gtm.ts
@@ -91,6 +91,7 @@ type ActionGtmEvent = GtmEvent & {
eventCategory: string
eventAction: string
eventLabel?: EventLabel
+ abTest?: string
}
type PageviewGtmEvent = GtmEvent & {
@@ -118,6 +119,10 @@ export const gtmTrack = (eventData: AnalyticsEvent): void => {
gtmEvent.eventLabel = eventData.label
}
+ if (eventData.abTest) {
+ gtmEvent.abTest = eventData.abTest()
+ }
+
gtmSend(gtmEvent)
}
diff --git a/src/services/analytics/types.ts b/src/services/analytics/types.ts
index 1898ed3bda..2307e586d1 100644
--- a/src/services/analytics/types.ts
+++ b/src/services/analytics/types.ts
@@ -15,6 +15,7 @@ export type AnalyticsEvent = {
category: string
action: string
label?: EventLabel
+ abTest?: () => string
}
export type SafeAppEvent = {
diff --git a/src/services/local-storage/useLocalStorage.ts b/src/services/local-storage/useLocalStorage.ts
index 94f3f68300..0ed61ef316 100644
--- a/src/services/local-storage/useLocalStorage.ts
+++ b/src/services/local-storage/useLocalStorage.ts
@@ -1,19 +1,38 @@
import type { Dispatch, SetStateAction } from 'react'
-import { useState, useCallback, useEffect } from 'react'
+import { useState, useCallback } from 'react'
import local from './local'
-const useLocalStorage = (key: string, initialState: T): [T, Dispatch>] => {
- const [cache, setCache] = useState(initialState)
+/**
+ * Locally persisted equivalent of `useState` that saves to `localStorage` when `setNewValue` is called
+ * or (initially) when `shouldPersistInitialState` is `true`
+ *
+ * @param key `localStorage` key to store under
+ * @param initialState default state to return if no `localStorage` value exists
+ * @param shouldPersistInitialState if no `localStorage` value exists, persist the `initialState` in `localStorage`
+ * @returns persisted state if it exists, otherwise `initialState`
+ */
- useEffect(() => {
- const initialValue = local.getItem(key)
- if (initialValue !== undefined) {
- setCache(initialValue)
+const useLocalStorage = (
+ key: string,
+ initialState: T,
+ shouldPersistInitialState = false,
+): [T, Dispatch>] => {
+ const [cache, setCache] = useState(() => {
+ const value = local.getItem(key)
+
+ if (value !== undefined) {
+ return value
+ }
+
+ if (shouldPersistInitialState) {
+ local.setItem(key, initialState)
}
- }, [setCache, key])
- const setNewValue = useCallback(
- (value: T | ((prevState: T) => T)) => {
+ return initialState
+ })
+
+ const setNewValue: Dispatch> = useCallback(
+ (value) => {
setCache((prevState) => {
const newState = value instanceof Function ? value(prevState) : value