mirror of
https://github.com/supabase/supabase.git
synced 2026-09-09 11:30:17 +08:00
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 -->
145 lines
5.1 KiB
TypeScript
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>
|
|
)
|
|
}
|