mirror of
https://github.com/supabase/supabase.git
synced 2026-05-20 04:23:53 +08:00
This PR renames all `SUPABASE_PUBLISHABLE_OR_ANON_KEY` env vars into `SUPABASE_PUBLISHABLE_KEY` to make the new API keys default. This is in coordination with the rest of the docs. I've also cleaned up the `blocks/vue` package from unused files. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Breaking Changes** * Public environment variable names renamed from PUBLISHABLE_OR_ANON_KEY → PUBLISHABLE_KEY across all framework integrations; update your environment configs. * **Documentation** * All framework guides, .env examples and registry docs updated to use the new variable names. * **Chores** * Cleaned up UI registry/templates: some example Vue registry items and autogenerated registry artifacts were removed or simplified. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
53 lines
11 KiB
JSON
53 lines
11 KiB
JSON
{
|
|
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
"name": "realtime-chat-tanstack",
|
|
"type": "registry:component",
|
|
"title": "Realtime Chat",
|
|
"description": "Component which renders realtime chat messages from other users in a room.",
|
|
"dependencies": [
|
|
"lucide-react",
|
|
"@supabase/ssr@latest",
|
|
"@supabase/supabase-js@latest"
|
|
],
|
|
"registryDependencies": [
|
|
"input",
|
|
"button"
|
|
],
|
|
"files": [
|
|
{
|
|
"path": "registry/default/blocks/realtime-chat/components/chat-message.tsx",
|
|
"content": "import { cn } from '@/lib/utils'\nimport type { ChatMessage } from '@/registry/default/blocks/realtime-chat/hooks/use-realtime-chat'\n\ninterface ChatMessageItemProps {\n message: ChatMessage\n isOwnMessage: boolean\n showHeader: boolean\n}\n\nexport const ChatMessageItem = ({ message, isOwnMessage, showHeader }: ChatMessageItemProps) => {\n return (\n <div className={`flex mt-2 ${isOwnMessage ? 'justify-end' : 'justify-start'}`}>\n <div\n className={cn('max-w-[75%] w-fit flex flex-col gap-1', {\n 'items-end': isOwnMessage,\n })}\n >\n {showHeader && (\n <div\n className={cn('flex items-center gap-2 text-xs px-3', {\n 'justify-end flex-row-reverse': isOwnMessage,\n })}\n >\n <span className={'font-medium'}>{message.user.name}</span>\n <span className=\"text-foreground/50 text-xs\">\n {new Date(message.createdAt).toLocaleTimeString('en-US', {\n hour: '2-digit',\n minute: '2-digit',\n hour12: true,\n })}\n </span>\n </div>\n )}\n <div\n className={cn(\n 'py-2 px-3 rounded-xl text-sm w-fit',\n isOwnMessage ? 'bg-primary text-primary-foreground' : 'bg-muted text-foreground'\n )}\n >\n {message.content}\n </div>\n </div>\n </div>\n )\n}\n",
|
|
"type": "registry:component"
|
|
},
|
|
{
|
|
"path": "registry/default/blocks/realtime-chat/components/realtime-chat.tsx",
|
|
"content": "'use client'\n\nimport { Send } from 'lucide-react'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { ChatMessageItem } from '@/registry/default/blocks/realtime-chat/components/chat-message'\nimport { useChatScroll } from '@/registry/default/blocks/realtime-chat/hooks/use-chat-scroll'\nimport {\n useRealtimeChat,\n type ChatMessage,\n} from '@/registry/default/blocks/realtime-chat/hooks/use-realtime-chat'\nimport { Button } from '@/registry/default/components/ui/button'\nimport { Input } from '@/registry/default/components/ui/input'\n\ninterface RealtimeChatProps {\n roomName: string\n username: string\n onMessage?: (messages: ChatMessage[]) => void\n messages?: ChatMessage[]\n}\n\n/**\n * Realtime chat component\n * @param roomName - The name of the room to join. Each room is a unique chat.\n * @param username - The username of the user\n * @param onMessage - The callback function to handle the messages. Useful if you want to store the messages in a database.\n * @param messages - The messages to display in the chat. Useful if you want to display messages from a database.\n * @returns The chat component\n */\nexport const RealtimeChat = ({\n roomName,\n username,\n onMessage,\n messages: initialMessages = [],\n}: RealtimeChatProps) => {\n const { containerRef, scrollToBottom } = useChatScroll()\n\n const {\n messages: realtimeMessages,\n sendMessage,\n isConnected,\n } = useRealtimeChat({\n roomName,\n username,\n })\n const [newMessage, setNewMessage] = useState('')\n\n // Merge realtime messages with initial messages\n const allMessages = useMemo(() => {\n const mergedMessages = [...initialMessages, ...realtimeMessages]\n // Remove duplicates based on message id\n const uniqueMessages = mergedMessages.filter(\n (message, index, self) => index === self.findIndex((m) => m.id === message.id)\n )\n // Sort by creation date\n const sortedMessages = uniqueMessages.sort((a, b) => a.createdAt.localeCompare(b.createdAt))\n\n return sortedMessages\n }, [initialMessages, realtimeMessages])\n\n useEffect(() => {\n if (onMessage) {\n onMessage(allMessages)\n }\n }, [allMessages, onMessage])\n\n useEffect(() => {\n // Scroll to bottom whenever messages change\n scrollToBottom()\n }, [allMessages, scrollToBottom])\n\n const handleSendMessage = useCallback(\n (e: React.FormEvent) => {\n e.preventDefault()\n if (!newMessage.trim() || !isConnected) return\n\n sendMessage(newMessage)\n setNewMessage('')\n },\n [newMessage, isConnected, sendMessage]\n )\n\n return (\n <div className=\"flex flex-col h-full w-full bg-background text-foreground antialiased\">\n {/* Messages */}\n <div ref={containerRef} className=\"flex-1 overflow-y-auto p-4 space-y-4\">\n {allMessages.length === 0 ? (\n <div className=\"text-center text-sm text-muted-foreground\">\n No messages yet. Start the conversation!\n </div>\n ) : null}\n <div className=\"space-y-1\">\n {allMessages.map((message, index) => {\n const prevMessage = index > 0 ? allMessages[index - 1] : null\n const showHeader = !prevMessage || prevMessage.user.name !== message.user.name\n\n return (\n <div\n key={message.id}\n className=\"animate-in fade-in slide-in-from-bottom-4 duration-300\"\n >\n <ChatMessageItem\n message={message}\n isOwnMessage={message.user.name === username}\n showHeader={showHeader}\n />\n </div>\n )\n })}\n </div>\n </div>\n\n <form onSubmit={handleSendMessage} className=\"flex w-full gap-2 border-t border-border p-4\">\n <Input\n className={cn(\n 'rounded-full bg-background text-sm transition-all duration-300',\n isConnected && newMessage.trim() ? 'w-[calc(100%-36px)]' : 'w-full'\n )}\n type=\"text\"\n value={newMessage}\n onChange={(e) => setNewMessage(e.target.value)}\n placeholder=\"Type a message...\"\n disabled={!isConnected}\n />\n {isConnected && newMessage.trim() && (\n <Button\n className=\"aspect-square rounded-full animate-in fade-in slide-in-from-right-4 duration-300\"\n type=\"submit\"\n disabled={!isConnected}\n >\n <Send className=\"size-4\" />\n </Button>\n )}\n </form>\n </div>\n )\n}\n",
|
|
"type": "registry:component"
|
|
},
|
|
{
|
|
"path": "registry/default/blocks/realtime-chat/hooks/use-realtime-chat.tsx",
|
|
"content": "'use client'\n\nimport { useCallback, useEffect, useState } from 'react'\n\nimport { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client'\n\ninterface UseRealtimeChatProps {\n roomName: string\n username: string\n}\n\nexport interface ChatMessage {\n id: string\n content: string\n user: {\n name: string\n }\n createdAt: string\n}\n\nconst EVENT_MESSAGE_TYPE = 'message'\n\nexport function useRealtimeChat({ roomName, username }: UseRealtimeChatProps) {\n const supabase = createClient()\n const [messages, setMessages] = useState<ChatMessage[]>([])\n const [channel, setChannel] = useState<ReturnType<typeof supabase.channel> | null>(null)\n const [isConnected, setIsConnected] = useState(false)\n\n useEffect(() => {\n const newChannel = supabase.channel(roomName)\n\n newChannel\n .on('broadcast', { event: EVENT_MESSAGE_TYPE }, (payload) => {\n setMessages((current) => [...current, payload.payload as ChatMessage])\n })\n .subscribe(async (status) => {\n if (status === 'SUBSCRIBED') {\n setIsConnected(true)\n } else {\n setIsConnected(false)\n }\n })\n\n setChannel(newChannel)\n\n return () => {\n supabase.removeChannel(newChannel)\n }\n }, [roomName, username, supabase])\n\n const sendMessage = useCallback(\n async (content: string) => {\n if (!channel || !isConnected) return\n\n const message: ChatMessage = {\n id: crypto.randomUUID(),\n content,\n user: {\n name: username,\n },\n createdAt: new Date().toISOString(),\n }\n\n // Update local state immediately for the sender\n setMessages((current) => [...current, message])\n\n await channel.send({\n type: 'broadcast',\n event: EVENT_MESSAGE_TYPE,\n payload: message,\n })\n },\n [channel, isConnected, username]\n )\n\n return { messages, sendMessage, isConnected }\n}\n",
|
|
"type": "registry:hook"
|
|
},
|
|
{
|
|
"path": "registry/default/blocks/realtime-chat/hooks/use-chat-scroll.tsx",
|
|
"content": "import { useCallback, useRef } from 'react'\n\nexport function useChatScroll() {\n const containerRef = useRef<HTMLDivElement>(null)\n\n const scrollToBottom = useCallback(() => {\n if (!containerRef.current) return\n\n const container = containerRef.current\n container.scrollTo({\n top: container.scrollHeight,\n behavior: 'smooth',\n })\n }, [])\n\n return { containerRef, scrollToBottom }\n}\n",
|
|
"type": "registry:hook"
|
|
},
|
|
{
|
|
"path": "registry/default/clients/tanstack/lib/supabase/client.ts",
|
|
"content": "/// <reference types=\"vite/types/importMeta.d.ts\" />\nimport { createBrowserClient } from '@supabase/ssr'\n\nexport function createClient() {\n return createBrowserClient(\n import.meta.env.VITE_SUPABASE_URL!,\n import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY!\n )\n}\n",
|
|
"type": "registry:lib"
|
|
},
|
|
{
|
|
"path": "registry/default/clients/tanstack/lib/supabase/server.ts",
|
|
"content": "import { createServerClient } from '@supabase/ssr'\nimport { getCookies, setCookie } from '@tanstack/react-start/server'\n\nexport function createClient() {\n return createServerClient(\n process.env.VITE_SUPABASE_URL!,\n process.env.VITE_SUPABASE_PUBLISHABLE_KEY!,\n {\n cookies: {\n getAll() {\n return Object.entries(getCookies()).map(\n ([name, value]) =>\n ({\n name,\n value,\n }) as { name: string; value: string }\n )\n },\n setAll(cookies) {\n cookies.forEach((cookie) => {\n setCookie(cookie.name, cookie.value)\n })\n },\n },\n }\n )\n}\n",
|
|
"type": "registry:lib"
|
|
}
|
|
],
|
|
"envVars": {
|
|
"VITE_SUPABASE_URL": "",
|
|
"VITE_SUPABASE_PUBLISHABLE_KEY": ""
|
|
},
|
|
"docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_KEY`."
|
|
} |