Files
supabase/apps/ui-library/components/tanstack-db-generator/index.tsx
Saxon Fletcher cb35e1f98e chore(library): update routes, redirects, and naming (#48668)
Our UI Library registry is expanding to include blocks that go beyond UI
and in some cases focus purely on back-end. This PR is a precursor to
adding more back-end related blocks. This PR includes the `ui-library ->
library` rename plus redirects and small UI copy updates. Since this is
a rename we'll need to update Vercel configuration.

## Vercel rollout

Keep the Library project Root Directory as `apps/ui-library`

1. In the **Library** Vercel project, set:

   `NEXT_PUBLIC_BASE_PATH=/library`

Apply it to Preview and Production, then redeploy the Library project.

2. In the **www** Vercel project, add:

`NEXT_PUBLIC_LIBRARY_URL=<current value of NEXT_PUBLIC_UI_LIBRARY_URL>`

Apply it to Preview and Production. Keep `NEXT_PUBLIC_UI_LIBRARY_URL`
during the migration, then redeploy the www project.

3. Deploy in this order:

   1. Library project
   2. www project

4. Validate:

   - `/library`
   - `/library/docs/nextjs/password-based-auth`
   - `/ui` redirects to `/library`
- `/ui/docs/nextjs/password-based-auth` redirects to
`/library/docs/nextjs/password-based-auth`
- `/ui/docs/ai-editors-rules/*` still uses its existing Docs redirects

No Vercel dashboard redirect rules are needed. Environment-variable
changes require a new deployment.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Supabase UI Library has been renamed to **Supabase Library** across
navigation, pages, documentation, and resource links.
* The Library is now available at `/library`, with updated descriptions
covering components, blocks, and developer tools.
* **Bug Fixes**
* Added permanent redirects from legacy `/ui` URLs to corresponding
`/library` paths.
* Updated links throughout the site and documentation to prevent broken
navigation and references.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-11 13:37:32 +10:00

145 lines
5.1 KiB
TypeScript

'use client'
import { useIsLoggedIn, useIsUserLoading } from 'common'
import { Check, Copy, Loader2 } from 'lucide-react'
import { useCallback, useState } from 'react'
import { Button, cn, copyToClipboard, Tabs, TabsContent, TabsList, TabsTrigger } from 'ui'
import { useLocalStorage } from '../use-local-storage'
import { ProjectPicker } from './ProjectPicker'
type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun'
const LOCAL_STORAGE_KEY = 'package-manager-tanstack-db'
const getBaseUrl = () => {
if (process.env.NEXT_PUBLIC_VERCEL_TARGET_ENV === 'production') {
return 'https://supabase.com/library'
} else if (process.env.NEXT_PUBLIC_VERCEL_TARGET_ENV === 'preview') {
return `https://${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL}`
} else {
return 'http://localhost:3004/library'
}
}
export function TanstackDBGenerator() {
const isUserLoading = useIsUserLoading()
const isLoggedIn = useIsLoggedIn()
const [resolvedProject, setResolvedProject] = useState<{
projectRef: string
anonKey: string
} | null>(null)
const [hasCopied, setHasCopied] = useState(false)
const [packageManager, setPackageManager] = useLocalStorage(LOCAL_STORAGE_KEY, 'npm')
const handleProjectResolved = useCallback(
(data: { projectRef: string; anonKey: string } | null) => {
setResolvedProject(data)
},
[]
)
const baseUrl = getBaseUrl()
const apiUrl = resolvedProject
? `${baseUrl}/api/registry/tanstack-db?ref=${encodeURIComponent(resolvedProject.projectRef)}&anonKey=${encodeURIComponent(resolvedProject.anonKey)}`
: ''
const commands: Record<PackageManager, string> = {
npm: `npx shadcn@latest add "${apiUrl}"`,
pnpm: `pnpm dlx shadcn@latest add "${apiUrl}"`,
yarn: `yarn dlx shadcn@latest add "${apiUrl}"`,
bun: `bunx --bun shadcn@latest add "${apiUrl}"`,
}
const handleCopy = (command: string) => {
copyToClipboard(command)
setHasCopied(true)
setTimeout(() => setHasCopied(false), 2000)
}
if (isUserLoading) {
return (
<div className="mt-6 flex items-center gap-2 text-sm text-muted-foreground">
<Loader2 className="h-4 w-4 animate-spin" />
Loading...
</div>
)
}
if (!isLoggedIn) {
return (
<div className="mt-6 rounded-md border border-default bg-surface-100 p-4">
<p className="text-sm text-foreground-light">
<a
href="https://supabase.com/dashboard"
target="_blank"
rel="noreferrer noopener"
className="text-foreground underline"
>
Log in
</a>{' '}
to your Supabase account to generate installation commands for your project.
</p>
</div>
)
}
return (
<div className="mt-6 space-y-4">
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">Select a project</label>
<ProjectPicker onProjectResolved={handleProjectResolved} />
</div>
{resolvedProject && (
<>
<div className="rounded-md bg-brand-400/10 border border-brand-400/20 p-3 text-sm text-brand-600">
Copy the command below to install the block.
</div>
<Tabs value={packageManager} onValueChange={setPackageManager} className="w-full">
<div className="w-full group relative rounded-lg bg-surface-200 dark:bg-surface-100 px-4 py-2 overflow-hidden">
<div className="flex flex-col">
<TabsList className="gap-2 relative mb-2 z-10">
{(Object.keys(commands) as PackageManager[]).map((manager) => (
<TabsTrigger key={manager} value={manager} className="text-xs">
{manager}
</TabsTrigger>
))}
</TabsList>
{(Object.keys(commands) as PackageManager[]).map((manager) => (
<TabsContent key={manager} value={manager} className="m-0">
<div className="flex items-center gap-2">
<div className="flex-1 font-mono text-sm text-foreground relative z-10 overflow-x-auto">
<span className="mr-2 text-[#888] select-none">$</span>
<span className="whitespace-nowrap">{commands[manager]}</span>
</div>
<Button
size="small"
variant="outline"
className={cn(
'relative z-10 h-6 w-6 text-foreground-muted hover:bg-surface-100 hover:text-foreground p-0 shrink-0'
)}
onClick={() => handleCopy(commands[manager])}
>
<span className="sr-only">Copy</span>
{hasCopied ? (
<Check className="h-3 w-3 text-brand-600" />
) : (
<Copy className="h-3 w-3" />
)}
</Button>
</div>
</TabsContent>
))}
</div>
</div>
</Tabs>
</>
)}
</div>
)
}