diff --git a/package.json b/package.json index 692d1c5..55ff599 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "linkdex": "^3.0.0", "multiformats": "^12.1.3", "next": "^13.5.4", + "next-plausible": "^3.12.4", "nft.storage": "^7.1.1", "p-retry": "^6.2.0", "react": "latest", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3ac8a8d..3d857e5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -74,6 +74,9 @@ importers: next: specifier: ^13.5.4 version: 13.5.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + next-plausible: + specifier: ^3.12.4 + version: 3.12.4(next@13.5.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) nft.storage: specifier: ^7.1.1 version: 7.1.1(encoding@0.1.13)(node-fetch@2.7.0(encoding@0.1.13)) @@ -683,6 +686,10 @@ packages: '@sinclair/typebox@0.25.24': resolution: {integrity: sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==} + '@storacha/one-webcrypto@https://codeload.github.com/web3-storage/one-webcrypto/tar.gz/9e029e2fd477bd95bb80abd3553bbac704ccc7a6': + resolution: {tarball: https://codeload.github.com/web3-storage/one-webcrypto/tar.gz/9e029e2fd477bd95bb80abd3553bbac704ccc7a6} + version: 1.0.1 + '@swc/helpers@0.5.2': resolution: {integrity: sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==} @@ -2756,6 +2763,13 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + next-plausible@3.12.4: + resolution: {integrity: sha512-cD3+ixJxf8yBYvsideTxqli3fvrB7R4BXcvsNJz8Sm2X1QN039WfiXjCyNWkub4h5++rRs6fHhchUMnOuJokcg==} + peerDependencies: + next: '^11.1.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 ' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + next@13.5.4: resolution: {integrity: sha512-+93un5S779gho8y9ASQhb/bTkQF17FNQOtXLKAj3lsNgltEcF0C5PMLLncDmH+8X1EnJH1kbqAERa29nRXqhjA==} engines: {node: '>=16.14.0'} @@ -2885,10 +2899,6 @@ packages: one-webcrypto@1.0.3: resolution: {integrity: sha512-fu9ywBVBPx0gS9K0etIROTiCkvI5S1TDjFsYFb3rC1ewFxeOqsbzq7aIMBHsYfrTHBcGXJaONXXjTl8B01cW1Q==} - one-webcrypto@https://codeload.github.com/web3-storage/one-webcrypto/tar.gz/5148cd14d5489a8ac4cd38223870e02db15a2382: - resolution: {tarball: https://codeload.github.com/web3-storage/one-webcrypto/tar.gz/5148cd14d5489a8ac4cd38223870e02db15a2382} - version: 1.0.3 - onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} @@ -4277,6 +4287,8 @@ snapshots: '@sinclair/typebox@0.25.24': {} + '@storacha/one-webcrypto@https://codeload.github.com/web3-storage/one-webcrypto/tar.gz/9e029e2fd477bd95bb80abd3553bbac704ccc7a6': {} + '@swc/helpers@0.5.2': dependencies: tslib: 2.6.2 @@ -4707,7 +4719,7 @@ snapshots: bigint-mod-arith: 3.3.1 conf: 11.0.2 multiformats: 12.1.3 - one-webcrypto: https://codeload.github.com/web3-storage/one-webcrypto/tar.gz/5148cd14d5489a8ac4cd38223870e02db15a2382 + one-webcrypto: '@storacha/one-webcrypto@https://codeload.github.com/web3-storage/one-webcrypto/tar.gz/9e029e2fd477bd95bb80abd3553bbac704ccc7a6' p-defer: 4.0.1 type-fest: 4.18.2 uint8arrays: 4.0.9 @@ -4728,7 +4740,7 @@ snapshots: bigint-mod-arith: 3.3.1 conf: 11.0.2 multiformats: 12.1.3 - one-webcrypto: https://codeload.github.com/web3-storage/one-webcrypto/tar.gz/5148cd14d5489a8ac4cd38223870e02db15a2382 + one-webcrypto: '@storacha/one-webcrypto@https://codeload.github.com/web3-storage/one-webcrypto/tar.gz/9e029e2fd477bd95bb80abd3553bbac704ccc7a6' p-defer: 4.0.1 type-fest: 4.18.2 uint8arrays: 4.0.9 @@ -6883,6 +6895,12 @@ snapshots: natural-compare@1.4.0: {} + next-plausible@3.12.4(next@13.5.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + dependencies: + next: 13.5.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + next@13.5.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@next/env': 13.5.4 @@ -7040,8 +7058,6 @@ snapshots: one-webcrypto@1.0.3: {} - one-webcrypto@https://codeload.github.com/web3-storage/one-webcrypto/tar.gz/5148cd14d5489a8ac4cd38223870e02db15a2382: {} - onetime@5.1.2: dependencies: mimic-fn: 2.1.0 diff --git a/src/app/layout.tsx b/src/app/layout.tsx index b07fc06..801e471 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,6 +3,7 @@ import type { Metadata } from 'next' import Provider from '@/components/W3UIProvider' import Toaster from '@/components/Toaster' import { Provider as MigrationsProvider } from '@/components/MigrationsProvider' +import PlausibleProvider from 'next-plausible' export const metadata: Metadata = { title: 'w3up console', @@ -17,12 +18,21 @@ export default function RootLayout ({ return ( - - - {children} - - - + + + + {children} + + + + ) diff --git a/src/app/logout/page.tsx b/src/app/logout/page.tsx index 746817b..b3aa78e 100644 --- a/src/app/logout/page.tsx +++ b/src/app/logout/page.tsx @@ -2,15 +2,18 @@ import { useW3 } from "@w3ui/react" import { useRouter } from "next/navigation" import { useEffect } from "react" +import { usePlausible } from 'next-plausible' export default function LogoutPage () { const [, { logout }] = useW3() + const plausible = usePlausible() const router = useRouter() useEffect(function () { if (logout) { async function logOutAndRedirect () { await logout() + plausible('Logout') router.push('/') } logOutAndRedirect() diff --git a/src/app/migration/create/page.tsx b/src/app/migration/create/page.tsx index 0591b07..134281b 100644 --- a/src/app/migration/create/page.tsx +++ b/src/app/migration/create/page.tsx @@ -10,6 +10,7 @@ import * as NFTStorageMigrator from '@/lib/migrations/nft-storage' import * as Web3StorageMigrator from '@/lib/migrations/web3-storage' import { DidIcon } from '@/components/DidIcon' import { MigrationConfiguration, MigrationSource } from '@/lib/migrations/api' +import { usePlausible } from 'next-plausible' interface WizardProps { config: Partial @@ -168,12 +169,18 @@ function ChooseTargetSpace ({ config, onNext, onPrev }: WizardProps) { } function Confirmation ({ config, onNext, onPrev }: WizardProps) { + const plausible = usePlausible() const [{ spaces }] = useW3() const space = spaces.find(s => s.did() === config.space) if (!space) return const handleNextClick: MouseEventHandler = async e => { e.preventDefault() + plausible('Migration Started', { + props: { + source: config.source + } + }) onNext(config) } return ( diff --git a/src/components/Authenticator.tsx b/src/components/Authenticator.tsx index ba31eda..26521d5 100644 --- a/src/components/Authenticator.tsx +++ b/src/components/Authenticator.tsx @@ -1,5 +1,6 @@ 'use client' - +import { usePlausible } from 'next-plausible'; +import { useEffect, useRef } from 'react' import { Authenticator as AuthCore, useAuthenticator @@ -8,6 +9,7 @@ import { Logo } from '../brand' import { TopLevelLoader } from './Loader' export function AuthenticationForm (): JSX.Element { + const plausible = usePlausible(); const [{ submitted }] = useAuthenticator() return (
@@ -24,6 +26,7 @@ export function AuthenticationForm (): JSX.Element { className='inline-block bg-zinc-950 hover:outline text-white font-bold text-sm px-6 py-2 rounded-full whitespace-nowrap' type='submit' disabled={submitted} + onClick={() => plausible('Login Authorization Requested')} > Authorize @@ -37,6 +40,7 @@ export function AuthenticationForm (): JSX.Element { } export function AuthenticationSubmitted (): JSX.Element { + const plausible = usePlausible(); const [{ email }] = useAuthenticator() return ( @@ -49,9 +53,11 @@ export function AuthenticationSubmitted (): JSX.Element {

Click the link in the email we sent to {email} to authorize this agent.

- - Cancel - + plausible('Login Authorization Cancelled')}> + + Cancel + +
) @@ -63,7 +69,22 @@ export function AuthenticationEnsurer ({ children: JSX.Element | JSX.Element[] }): JSX.Element { const [{ submitted, accounts, client }] = useAuthenticator() + const plausible = usePlausible() const authenticated = !!accounts.length + const previousAuth = useRef(authenticated) + + useEffect(() => { + console.debug('auth changed:', { + was: previousAuth.current, + now: authenticated + }) + // Only track if the transition is from unauthenticated ➝ authenticated + if (!previousAuth.current && authenticated) { + plausible('Login Successful') + } + previousAuth.current = authenticated + }, [authenticated, plausible]) + if (authenticated) { return <>{children} } diff --git a/src/components/SpaceCreator.tsx b/src/components/SpaceCreator.tsx index 1e3b503..8e5f8bc 100644 --- a/src/components/SpaceCreator.tsx +++ b/src/components/SpaceCreator.tsx @@ -6,6 +6,7 @@ import Loader from '../components/Loader' import { DID, DIDKey } from '@ucanto/interface' import { DidIcon } from './DidIcon' import Link from 'next/link' +import { usePlausible } from 'next-plausible' export function SpaceCreatorCreating (): JSX.Element { return ( @@ -28,6 +29,7 @@ export function SpaceCreatorForm ({ const [created, setCreated] = useState(false) const [name, setName] = useState('') const [space, setSpace] = useState() + const plausible = usePlausible() function resetForm (): void { setName('') @@ -73,10 +75,12 @@ export function SpaceCreatorForm ({ setSpace(client.spaces().find(s => s.did() === space.did())) setCreated(true) + plausible('Space Created') resetForm() } catch (error) { /* eslint-disable-next-line no-console */ console.error(error) + plausible('Failed Space Creation') throw new Error('failed to create space', { cause: error }) } }