diff --git a/apps/ui-library/.env b/apps/ui-library/.env
index 69c207b2cd1..47ab9bf34ed 100644
--- a/apps/ui-library/.env
+++ b/apps/ui-library/.env
@@ -1,4 +1,5 @@
# Default to local supabase for local development
NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0
-NEXT_PUBLIC_BASE_PATH=/ui
\ No newline at end of file
+NEXT_PUBLIC_BASE_PATH=/ui
+NEXT_PUBLIC_API_URL="http://localhost:8080/platform"
\ No newline at end of file
diff --git a/apps/ui-library/app/(app)/layout.tsx b/apps/ui-library/app/(app)/layout.tsx
index 32526719839..e4706682165 100644
--- a/apps/ui-library/app/(app)/layout.tsx
+++ b/apps/ui-library/app/(app)/layout.tsx
@@ -1,5 +1,6 @@
import { Sidebar } from '@/components/sidebar'
import { SiteFooter } from '@/components/site-footer'
+import { TelemetryWrapper } from './telemetry-wrapper'
interface AppLayoutProps {
children: React.ReactNode
@@ -24,6 +25,7 @@ export default function AppLayout({ children }: AppLayoutProps) {
+
>
)
}
diff --git a/apps/ui-library/app/(app)/telemetry-wrapper.tsx b/apps/ui-library/app/(app)/telemetry-wrapper.tsx
new file mode 100644
index 00000000000..5460e46156b
--- /dev/null
+++ b/apps/ui-library/app/(app)/telemetry-wrapper.tsx
@@ -0,0 +1,16 @@
+'use client'
+
+import { API_URL } from '@/lib/constants'
+import { IS_PLATFORM, PageTelemetry } from 'common'
+import { useConsent } from 'ui-patterns/ConsentToast'
+
+export const TelemetryWrapper = () => {
+ const { hasAcceptedConsent } = useConsent()
+ return (
+
+ )
+}
diff --git a/apps/ui-library/app/Providers.tsx b/apps/ui-library/app/Providers.tsx
index 97e0ebdbc72..d6083701403 100644
--- a/apps/ui-library/app/Providers.tsx
+++ b/apps/ui-library/app/Providers.tsx
@@ -1,13 +1,14 @@
'use client'
-import { AuthProvider } from 'common'
import { Provider as JotaiProvider } from 'jotai'
import { ThemeProvider as NextThemesProvider } from 'next-themes'
import { ThemeProviderProps } from 'next-themes/dist/types'
import { FrameworkProvider } from '@/context/framework-context'
import { MobileMenuProvider } from '@/context/mobile-menu-context'
+import { AuthProvider } from 'common'
import { TooltipProvider } from 'ui'
+import { useConsent } from 'ui-patterns/ConsentToast'
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return (
diff --git a/apps/ui-library/components/command-copy-button.tsx b/apps/ui-library/components/command-copy-button.tsx
index d040a80a10d..a5fff1b356d 100644
--- a/apps/ui-library/components/command-copy-button.tsx
+++ b/apps/ui-library/components/command-copy-button.tsx
@@ -2,10 +2,13 @@
import { Check, Copy } from 'lucide-react'
import { useEffect, useState } from 'react'
+
+import { useSendTelemetryEvent } from 'lib/telemetry'
import { Button_Shadcn_ } from 'ui'
export function CommandCopyButton({ command }: { command: string }) {
const [copied, setCopied] = useState(false)
+ const sendTelemetryEvent = useSendTelemetryEvent()
useEffect(() => {
if (copied) {
@@ -14,13 +17,56 @@ export function CommandCopyButton({ command }: { command: string }) {
}
}, [copied])
+ const parseCommandForTelemetry = (cmd: string) => {
+ // Extract framework from URL (e.g., 'nextjs' from 'password-based-auth-nextjs.json')
+ const frameworkMatch = cmd.match(/\/ui\/r\/.*?-(\w+)\.json/)
+ const framework = frameworkMatch
+ ? (frameworkMatch[1] as 'nextjs' | 'react-router' | 'tanstack' | 'react')
+ : 'nextjs'
+
+ // Extract package manager from command prefix (npx, pnpm, yarn, bun)
+ const packageManager = cmd.startsWith('npx')
+ ? ('npm' as const)
+ : cmd.startsWith('pnpm')
+ ? ('pnpm' as const)
+ : cmd.startsWith('yarn')
+ ? ('yarn' as const)
+ : cmd.startsWith('bun')
+ ? ('bun' as const)
+ : ('npm' as const)
+
+ // Extract template title from URL (e.g., 'password-based-auth' from 'password-based-auth-nextjs.json')
+ const titleMatch = cmd.match(/\/ui\/r\/(.*?)-\w+\.json/)
+ const title = titleMatch ? titleMatch[1] : ''
+
+ return {
+ framework,
+ packageManager,
+ title,
+ }
+ }
+
return (
{
+ // Copy command to clipboard
navigator.clipboard.writeText(command)
setCopied(true)
+
+ // Parse command and send telemetry event
+ const { framework, packageManager, title } = parseCommandForTelemetry(command)
+
+ sendTelemetryEvent({
+ action: 'supabase_ui_command_copy_button_clicked',
+ properties: {
+ templateTitle: title,
+ command: command,
+ framework,
+ packageManager,
+ },
+ })
}}
>
{copied ? : }
diff --git a/apps/ui-library/lib/constants.ts b/apps/ui-library/lib/constants.ts
new file mode 100644
index 00000000000..9b2dc13741b
--- /dev/null
+++ b/apps/ui-library/lib/constants.ts
@@ -0,0 +1 @@
+export const API_URL = process.env.NEXT_PUBLIC_API_URL!
diff --git a/apps/ui-library/lib/telemetry.ts b/apps/ui-library/lib/telemetry.ts
new file mode 100644
index 00000000000..804549688cc
--- /dev/null
+++ b/apps/ui-library/lib/telemetry.ts
@@ -0,0 +1,19 @@
+'use client'
+
+import { usePathname } from 'next/navigation'
+import { useCallback } from 'react'
+
+import { sendTelemetryEvent } from 'common'
+import { TelemetryEvent } from 'common/telemetry-constants'
+import { API_URL } from 'lib/constants'
+
+export function useSendTelemetryEvent() {
+ const pathname = usePathname()
+
+ return useCallback(
+ (event: TelemetryEvent) => {
+ return sendTelemetryEvent(API_URL, event, pathname)
+ },
+ [pathname]
+ )
+}
diff --git a/packages/common/telemetry-constants.ts b/packages/common/telemetry-constants.ts
index 6182dba9e30..83cdcdca392 100644
--- a/packages/common/telemetry-constants.ts
+++ b/packages/common/telemetry-constants.ts
@@ -1223,6 +1223,23 @@ export interface EdgeFunctionTestSidePanelOpenedEvent {
}
}
+/**
+ * User copied the command for a Supabase UI component.
+ *
+ * @group Events
+ * @source supabase-ui
+ * @page /ui/docs/{framework}/{templateTitle}
+ */
+export interface SupabaseUiCommandCopyButtonClickedEvent {
+ action: 'supabase_ui_command_copy_button_clicked'
+ properties: {
+ templateTitle: string
+ command: string
+ framework: 'nextjs' | 'react-router' | 'tanstack' | 'react'
+ packageManager: 'npm' | 'pnpm' | 'yarn' | 'bun'
+ }
+}
+
/**
* @hidden
*/
@@ -1297,3 +1314,4 @@ export type TelemetryEvent =
| EdgeFunctionDeployUpdatesButtonClickedEvent
| EdgeFunctionTestSendButtonClickedEvent
| EdgeFunctionTestSidePanelOpenedEvent
+ | SupabaseUiCommandCopyButtonClickedEvent
diff --git a/turbo.json b/turbo.json
index adcba386ab3..61988aaa14a 100644
--- a/turbo.json
+++ b/turbo.json
@@ -130,6 +130,7 @@
"NEXT_PUBLIC_APP_URL",
"NEXT_PUBLIC_VERCEL_TARGET_ENV",
"NEXT_PUBLIC_VERCEL_BRANCH_URL",
+ "NEXT_PUBLIC_API_URL",
"NODE_ENV",
"HIDE_TAILWIND_INDICATOR",
// These envs are used in the packages