### Install shadcn/ui with Next.js Source: https://supabase.com/ui/docs/getting-started/quickstart Use this command to start a new Next.js project with Supabase UI integration. Ensure you have Node.js and npm/yarn installed. ```bash npx create-next-app -e with-supabase ``` -------------------------------- ### Install All Supabase Skills CLI Source: https://supabase.com/ui/docs/ai-editors-rules/skills Installs the full set of Supabase skills for most projects. Run this command in the root of your project. ```bash npx skills add supabase/agent-skills ``` -------------------------------- ### Install Infinite Query Hook Source: https://supabase.com/ui/docs/infinite-query-hook Install the infinite query hook using npm, yarn, or pnpm. ```bash npx shadcn@latest add @supabase/infinite-query-hook ``` -------------------------------- ### Install Platform Kit for Next.js Source: https://supabase.com/ui/docs/platform/platform-kit Use this command to add the Platform Kit to your Next.js project. Ensure you have npx, npm, yarn, or bun installed. ```bash npx shadcn@latest add @supabase/platform-kit-nextjs ``` -------------------------------- ### Install Supabase Postgres Best Practices Skill CLI Source: https://supabase.com/ui/docs/ai-editors-rules/skills Installs the Postgres-specific skill for database work, focusing on performance optimization, query design, and schema best practices. Use this in addition to the full set if you do extensive database work. ```bash npx skills add supabase/agent-skills --skill supabase-postgres-best-practices ``` -------------------------------- ### Install Current User Avatar Next.js Source: https://supabase.com/ui/docs/nextjs/current-user-avatar Install the Current User Avatar component for Next.js using npm, yarn, or bun. ```bash npx shadcn@latest add @supabase/current-user-avatar-nextjs ``` -------------------------------- ### Usage Example of CurrentUserAvatar Source: https://supabase.com/ui/docs/nextjs/current-user-avatar Demonstrates how to integrate the CurrentUserAvatar component into your Next.js application. This example shows it within a header component. ```typescript 'use client' import { CurrentUserAvatar } from '@/components/current-user-avatar' const CurrentUserAvatarDemo = () => { return (

Lumon Industries

) } export default CurrentUserAvatarDemo ``` -------------------------------- ### Install Supabase Dropzone for Next.js Source: https://supabase.com/ui/docs/nextjs/dropzone Use this command to add the Supabase Dropzone component to your Next.js project. ```bash npx shadcn@latest add @supabase/dropzone-nextjs ``` -------------------------------- ### Basic Realtime Monaco Usage Source: https://supabase.com/ui/docs/nextjs/realtime-monaco Demonstrates the basic setup for the Realtime Monaco component, specifying a channel and language. ```typescript import { RealtimeMonaco } from '@/components/realtime-monaco' export default function MonacoPage() { return } ``` -------------------------------- ### Install Realtime Avatar Stack for Next.js Source: https://supabase.com/ui/docs/nextjs/realtime-avatar-stack Use this command to add the Realtime Avatar Stack component to your Next.js project. ```bash npx shadcn@latest add @supabase/realtime-avatar-stack-nextjs ``` -------------------------------- ### Install Supabase Agent Skills as Claude Code Plugin Source: https://supabase.com/ui/docs/ai-editors-rules/skills Installs the Supabase agent skills as plugins for Claude Code. This allows Claude Code to discover and use the skills. ```bash /plugin marketplace add supabase/agent-skills ``` ```bash /plugin install supabase@supabase-agent-skills ``` -------------------------------- ### Realtime Monaco with Persistence Enabled Source: https://supabase.com/ui/docs/nextjs/realtime-monaco Example of using the Realtime Monaco component with persistence enabled by passing `true` to the `persistence` prop. ```typescript import { RealtimeMonaco } from '@/components/realtime-monaco' export default function MonacoPage() { return } ``` -------------------------------- ### InfiniteList Demo with Todo List Source: https://supabase.com/ui/docs/infinite-query-hook Demonstrates how to use the InfiniteList component with a Todo List data source. It defines a custom `renderItem` function to display individual todo items and a `trailingQuery` to sort the todos by insertion date. Ensure the Checkbox component from shadcn/ui is installed. ```typescript 'use client' import { InfiniteList } from './infinite-component' import { Checkbox } from '@/components/ui/checkbox' import { SupabaseQueryHandler } from '@/hooks/use-infinite-query' import { Database } from '@/lib/supabase.types' type TodoTask = Database['public']['Tables']['todos']['Row'] // Define how each item should be rendered const renderTodoItem = (todo: TodoTask) => { return (
{todo.task}
{new Date(todo.inserted_at).toLocaleDateString()}
) } const orderByInsertedAt: SupabaseQueryHandler<'todos'> = (query) => { return query.order('inserted_at', { ascending: false }) } export const InfiniteListDemo = () => { return (
) } ``` -------------------------------- ### Infinite Query Usage with Filtering on Search Params Source: https://supabase.com/ui/docs/infinite-query-hook Example of using the `useInfiniteQuery` hook with dynamic filtering based on URL search parameters. It shows how to apply `ilike` filtering to the query. ```typescript const params = useSearchParams() const searchQuery = params.get('q') const { data, isLoading, isFetching, fetchNextPage, count, isSuccess } = useInfiniteQuery({ tableName: 'products', columns: '*', pageSize: 10, trailingQuery: (query) => { if (searchQuery && searchQuery.length > 0) { query = query.ilike('name', `%${searchQuery}%`) } return query }, }) return (
{data.map((item) => ( ))}
) ``` -------------------------------- ### Infinite Query Usage with Sorting Source: https://supabase.com/ui/docs/infinite-query-hook Example of using the `useInfiniteQuery` hook to fetch data with sorting applied. It demonstrates how to use the `trailingQuery` prop for ordering results. ```typescript const { data, fetchNextPage } = useInfiniteQuery({ tableName: 'products', columns: '*', pageSize: 10, trailingQuery: (query) => query.order('created_at', { ascending: false }), }) return (
{data.map((item) => ( ))}
) ``` -------------------------------- ### Integrating RealtimeCursors Component in a Next.js Page Source: https://supabase.com/ui/docs/nextjs/realtime-cursor This example shows how to integrate the RealtimeCursors component into a Next.js page. Ensure the component is wrapped in 'use client' directive for client-side interactivity. Specify the roomName and username props for room identification and user labeling. ```typescript 'use client' import { RealtimeCursors } from '@/components/realtime-cursors' export default function Page() { return (
) } ``` -------------------------------- ### File Upload Demo Component Source: https://supabase.com/ui/docs/nextjs/dropzone Demonstrates how to use the Dropzone component with Supabase upload hooks. Configure bucket name, path, allowed MIME types, and file limits. ```javascript 'use client' import { Dropzone, DropzoneContent, DropzoneEmptyState } from '@/components/dropzone' import { useSupabaseUpload } from '@/hooks/use-supabase-upload' const FileUploadDemo = () => { const props = useSupabaseUpload({ bucketName: 'test', path: 'test', allowedMimeTypes: ['image/*'], maxFiles: 2, maxFileSize: 1000 * 1000 * 10, // 10MB, }) return (
) } export { FileUploadDemo } ``` -------------------------------- ### Create Supabase Browser Client Source: https://supabase.com/ui/docs/react-router/client Use this function to create a Supabase client for browser environments. Ensure VITE_SUPABASE_URL and VITE_SUPABASE_PUBLISHABLE_KEY are set in your .env.local file. ```typescript /// import { createBrowserClient } from '@supabase/ssr' export function createClient() { return createBrowserClient( import.meta.env.VITE_SUPABASE_URL!, import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY! ) } ``` -------------------------------- ### Realtime Chat with Message Persistence - JSX Source: https://supabase.com/ui/docs/nextjs/realtime-chat Illustrates how to handle new messages for persistence by using the `onMessage` callback. This allows storing messages in a database. ```jsx import { RealtimeChat } from '@/components/realtime-chat' import { useMessagesQuery } from '@/hooks/use-messages-query' import { storeMessages } from '@/lib/store-messages' export default function ChatPage() { const { data: messages } = useMessagesQuery() const handleMessage = async (messages: ChatMessage[]) => { // Store messages in your database await storeMessages(messages) } return } ``` -------------------------------- ### Realtime Chat with Initial Messages - JSX Source: https://supabase.com/ui/docs/nextjs/realtime-chat Shows how to initialize the RealtimeChat component with a list of existing messages. This is useful for displaying chat history upon loading. ```jsx import { RealtimeChat } from '@/components/realtime-chat' import { useMessagesQuery } from '@/hooks/use-messages-query' export default function ChatPage() { const { data: messages } = useMessagesQuery() return } ``` -------------------------------- ### DropzoneEmptyState Component Source: https://supabase.com/ui/docs/nextjs/dropzone Displays the initial empty state of the dropzone, prompting users to upload files. Shows upload instructions, maximum file count, and maximum file size if configured. ```javascript const DropzoneEmptyState = ({ className }: { className?: string }) => { const { maxFiles, maxFileSize, inputRef, isSuccess } = useDropzoneContext() if (isSuccess) { return null } return (

Upload{!!maxFiles && maxFiles > 1 ? ` ${maxFiles}` : ''} file {!maxFiles || maxFiles > 1 ? 's' : ''}

Drag and drop or{' '} inputRef.current?.click()} className="underline cursor-pointer transition hover:text-foreground" > select {maxFiles === 1 ? `file` : 'files'} {' '} to upload

{maxFileSize !== Number.POSITIVE_INFINITY && (

Maximum file size: {formatBytes(maxFileSize, 2)}

)}
) } ``` -------------------------------- ### Environment Variables for Supabase Management Source: https://supabase.com/ui/docs/platform/platform-kit Configure your environment variables in a `.env.local` file to enable Supabase management features, including AI queries. Ensure your personal access token and API keys are securely managed. ```dotenv SUPABASE_MANAGEMENT_API_TOKEN=your-personal-access-token NEXT_PUBLIC_ENABLE_AI_QUERIES=true OPENAI_API_KEY=your-openai-api-key ``` -------------------------------- ### useInfiniteQuery Hook Implementation Source: https://supabase.com/ui/docs/infinite-query-hook This hook fetches data from a Supabase table in pages, handling loading states, errors, and providing a way to fetch the next page. It uses a store-like pattern to manage state. ```typescript 'use client' import { PostgrestQueryBuilder, type PostgrestClientOptions } from '@supabase/postgrest-js' import { type SupabaseClient } from '@supabase/supabase-js' import { useEffect, useRef, useSyncExternalStore } from 'react' import { createClient } from '@/lib/supabase/client' const supabase = createClient() // The following types are used to make the hook type-safe. It extracts the database type from the supabase client. type SupabaseClientType = typeof supabase // Utility type to check if the type is any type IfAny = 0 extends 1 & T ? Y : N // Extracts the database type from the supabase client. If the supabase client doesn't have a type, it will fallback properly. type Database = SupabaseClientType extends SupabaseClient ? IfAny< U, { public: { Tables: Record Views: Record Functions: Record } }, U > : { public: { Tables: Record Views: Record Functions: Record } } // Change this to the database schema you want to use type DatabaseSchema = Database['public'] // Extracts the table names from the database type type SupabaseTableName = keyof DatabaseSchema['Tables'] // Extracts the table definition from the database type type SupabaseTableData = DatabaseSchema['Tables'][T]['Row'] // Default client options for PostgrestQueryBuilder type DefaultClientOptions = PostgrestClientOptions type SupabaseSelectBuilder = ReturnType< PostgrestQueryBuilder< DefaultClientOptions, DatabaseSchema, DatabaseSchema['Tables'][T], T >['select'] > // A function that modifies the query. Can be used to sort, filter, etc. If .range is used, it will be overwritten. type SupabaseQueryHandler = ( query: SupabaseSelectBuilder ) => SupabaseSelectBuilder interface UseInfiniteQueryProps { // The table name to query tableName: T // The columns to select, defaults to `*` columns?: string // The number of items to fetch per page, defaults to `20` pageSize?: number // A function that modifies the query. Can be used to sort, filter, etc. If .range is used, it will be overwritten. trailingQuery?: SupabaseQueryHandler } interface StoreState { data: TData[] count: number isSuccess: boolean isLoading: boolean isFetching: boolean error: Error | null hasInitialFetch: boolean } type Listener = () => void function createStore, T extends SupabaseTableName>( props: UseInfiniteQueryProps ) { const { tableName, columns = '*', pageSize = 20, trailingQuery } = props let state: StoreState = { data: [], count: 0, isSuccess: false, isLoading: false, isFetching: false, error: null, hasInitialFetch: false, } const listeners = new Set() const notify = () => { listeners.forEach((listener) => listener()) } const setState = (newState: Partial>) => { state = { ...state, ...newState } notify() } const fetchPage = async (skip: number) => { if (state.hasInitialFetch && (state.isFetching || state.count <= state.data.length)) return setState({ isFetching: true }) let query = supabase .from(tableName) .select(columns, { count: 'exact' }) as unknown as SupabaseSelectBuilder if (trailingQuery) { query = trailingQuery(query) } const { data: newData, count, error } = await query.range(skip, skip + pageSize - 1) if (error) { console.error('An unexpected error occurred:', error) setState({ error }) } else { setState({ data: [...state.data, ...(newData as TData[])], count: count || 0, isSuccess: true, error: null, }) } setState({ isFetching: false }) } const fetchNextPage = async () => { if (state.isFetching) return await fetchPage(state.data.length) } const initialize = async () => { setState({ isLoading: true, isSuccess: false, data: [] }) await fetchNextPage() setState({ isLoading: false, hasInitialFetch: true }) } return { getState: () => state, subscribe: (listener: Listener) => { listeners.add(listener) return () => listeners.delete(listener) }, fetchNextPage, ``` -------------------------------- ### AI SQL Generation API Route Source: https://supabase.com/ui/docs/platform/platform-kit This API route handles POST requests to generate SQL queries. It requires a 'prompt' and 'projectRef' in the request body. It first retrieves the database schema, formats it, and then uses OpenAI to generate the SQL query. Error handling is included for missing parameters, permission issues, and AI generation failures. ```typescript 1import { NextResponse } from 'next/server' import OpenAI from 'openai' import createClient from 'openapi-fetch' import type { paths } from '@/platform/platform-kit-nextjs/lib/management-api-schema' import { listTablesSql } from '@/platform/platform-kit-nextjs/lib/pg-meta' const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }) const client = createClient({ baseUrl: 'https://api.supabase.com', headers: { Authorization: `Bearer ${process.env.SUPABASE_MANAGEMENT_API_TOKEN}`, }, }) // Function to get database schema async function getDbSchema(projectRef: string) { const token = process.env.SUPABASE_MANAGEMENT_API_TOKEN if (!token) { throw new Error('Supabase Management API token is not configured.') } const sql = listTablesSql() const { data, error } = await client.POST('/v1/projects/{ref}/database/query', { params: { path: { ref: projectRef, }, }, body: { query: sql, read_only: true, }, }) if (error) { throw error } return data as any } function formatSchemaForPrompt(schema: any) { let schemaString = '' if (schema && Array.isArray(schema)) { schema.forEach((table: any) => { const columnInfo = table.columns.map((c: any) => `${c.name} (${c.data_type})`) schemaString += `Table "${table.name}" has columns: ${columnInfo.join(', ')}.\n` }) } return schemaString } export async function POST(request: Request) { try { const { prompt, projectRef } = await request.json() if (!prompt) { return NextResponse.json({ message: 'Prompt is required.' }, { status: 400 }) } if (!projectRef) { return NextResponse.json({ message: 'projectRef is required.' }, { status: 400 }) } // Implement your permission check here (e.g. check if the user is a member of the project) // In this example, everyone can access all projects const userHasPermissionForProject = Boolean(projectRef) if (!userHasPermissionForProject) { return NextResponse.json( { message: 'You do not have permission to access this project.' }, { status: 403 } ) } // 1. Get database schema const schema = await getDbSchema(projectRef) const formattedSchema = formatSchemaForPrompt(schema) // 2. Create a prompt for OpenAI const systemPrompt = `You are an expert SQL assistant. Given the following database schema, write a SQL query that answers the user's question. Return only the SQL query, do not include any explanations or markdown.\n\nSchema:\n${formattedSchema}` // 3. Call OpenAI to generate SQL using responses.create (plain text output) const response = await openai.responses.create({ model: 'gpt-4.1', instructions: systemPrompt, // Use systemPrompt as instructions input: prompt, // User's question }) const sql = response.output_text if (!sql) { return NextResponse.json( { message: 'Could not generate SQL from the prompt.' }, { status: 500 } ) } // 4. Return the generated SQL return NextResponse.json({ sql }) } catch (error: any) { console.error('AI SQL generation error:', error) const errorMessage = error.message || 'An unexpected error occurred.' const status = error.response?.status || 500 return NextResponse.json({ message: errorMessage }, { status }) } } ``` -------------------------------- ### Realtime Monaco with Custom Persistence Options Source: https://supabase.com/ui/docs/nextjs/realtime-monaco Configures the Realtime Monaco component with specific persistence options, including table and column names, and a store timeout. ```jsx ``` -------------------------------- ### SupabasePersistenceOptions Configuration Source: https://supabase.com/ui/docs/nextjs/realtime-monaco Details the available options for configuring Supabase persistence for Yjs documents. ```APIDOC ## SupabasePersistenceOptions This object allows you to configure how Yjs documents are persisted to a Supabase database. ### Options | Option | Type | Default | Description | |---------------|---------|---------------|---------------------------------------------------| | `table` | `string`| `'yjs_documents'`| Name of the Postgres table used to store documents. | | `schema` | `string`| `'public'` | Schema where the table is located. | | `roomColumn` | `string`| `'room'` | Column used as the document identifier. | | `stateColumn` | `string`| `'state'` | Column used to store the binary Yjs state. | | `storeTimeout`| `number`| `1000` | Debounce delay (ms) before persisting changes. | ``` -------------------------------- ### Infinite Query Hook Implementation Source: https://supabase.com/ui/docs/infinite-query-hook The core implementation of the `useInfiniteQuery` hook. It manages data fetching, state synchronization, and initialization for progressive data loading from Supabase. ```typescript const initialState: any = { data: [], count: 0, isSuccess: false, isLoading: false, isFetching: false, error: null, hasInitialFetch: false, } function useInfiniteQuery, T extends SupabaseTableName = SupabaseTableName>( props: UseInfiniteQueryProps ) { const storeRef = useRef(createStore(props)) const state = useSyncExternalStore( storeRef.current.subscribe, () => storeRef.current.getState(), () => initialState as StoreState ) useEffect(() => { // Recreate store if props change if ( storeRef.current.getState().hasInitialFetch && ( props.tableName !== props.tableName || props.columns !== props.columns || props.pageSize !== props.pageSize ) ) { storeRef.current = createStore(props) } if (!state.hasInitialFetch && typeof window !== 'undefined') { storeRef.current.initialize() } }, [props.tableName, props.columns, props.pageSize, state.hasInitialFetch]) return { data: state.data, count: state.count, isSuccess: state.isSuccess, isLoading: state.isLoading, isFetching: state.isFetching, error: state.error, hasMore: state.count > state.data.length, fetchNextPage: storeRef.current.fetchNextPage, } } export { useInfiniteQuery, type SupabaseQueryHandler, type SupabaseTableData, type SupabaseTableName, type UseInfiniteQueryProps, } ``` -------------------------------- ### Basic Realtime Chat Usage - JSX Source: https://supabase.com/ui/docs/nextjs/realtime-chat Demonstrates the basic integration of the RealtimeChat component into a Next.js application. Requires specifying a room name and username. ```jsx import { RealtimeChat } from '@/components/realtime-chat' export default function ChatPage() { return } ``` -------------------------------- ### Sign-up Email Template HTML Source: https://supabase.com/ui/docs/nextjs/password-based-auth HTML structure for the sign-up confirmation email template in Supabase. Includes placeholders for site URL, token hash, and redirect URL. ```html

Confirm your signup

Follow this link to confirm your user:

Confirm your email

``` -------------------------------- ### Create Table for Persistence Source: https://supabase.com/ui/docs/nextjs/realtime-monaco SQL statement to create the necessary table in Supabase for persisting Yjs document state. ```sql create table yjs_documents ( room text primary key, state text not null ); ``` -------------------------------- ### Create Supabase Browser Client for Next.js Source: https://supabase.com/ui/docs/nextjs/client Use this function to create a Supabase client for browser-side operations in your Next.js application. Ensure NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY environment variables are set. ```typescript import { createBrowserClient } from '@supabase/ssr' export function createClient() { return createBrowserClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY! ) } ``` -------------------------------- ### Usage of RealtimeAvatarStack Component Source: https://supabase.com/ui/docs/nextjs/realtime-avatar-stack Integrate the RealtimeAvatarStack component into your Next.js application to display online users. Pass a `roomName` prop to specify the Supabase Realtime room. ```typescript import { RealtimeAvatarStack } from '@/components/realtime-avatar-stack' export default function Page() { return (

Lumon Industries

) } ``` -------------------------------- ### Supabase UI File Upload Component Props Source: https://supabase.com/ui/docs/nextjs/dropzone Configuration options for the Supabase UI file upload component. ```APIDOC ## Props ### `bucketName` * **Type**: `string` * **Default**: `null` * **Description**: The name of the Supabase Storage bucket to upload to. ### `path` * **Type**: `string` * **Default**: `null` * **Description**: The path or subfolder to upload the file to. ### `allowedMimeTypes` * **Type**: `string[]` * **Default**: `[]` * **Description**: The MIME types to allow for upload. ### `maxFiles` * **Type**: `number` * **Default**: `1` * **Description**: Maximum number of files to upload. ### `maxFileSize` * **Type**: `number` * **Default**: `1000` * **Description**: Maximum file size in bytes. ``` -------------------------------- ### InfiniteList Component Implementation Source: https://supabase.com/ui/docs/infinite-query-hook This component abstracts the useInfiniteQuery hook into a reusable UI element for infinite scrolling lists. It includes utility components for no results, end of list messages, and skeleton loaders. It uses an Intersection Observer to trigger fetching more data when the user scrolls near the bottom. ```typescript 'use client' import * as React from 'react' import { SupabaseQueryHandler, SupabaseTableData, SupabaseTableName, useInfiniteQuery, } from '@/hooks/use-infinite-query' import { cn } from '@/lib/utils' interface InfiniteListProps { tableName: TableName columns?: string pageSize?: number trailingQuery?: SupabaseQueryHandler renderItem: (item: SupabaseTableData, index: number) => React.ReactNode className?: string renderNoResults?: () => React.ReactNode renderEndMessage?: () => React.ReactNode renderSkeleton?: (count: number) => React.ReactNode } const DefaultNoResults = () => (
No results.
) const DefaultEndMessage = () => (
You've reached the end.
) const defaultSkeleton = (count: number) => (
{Array.from({ length: count }).map((_, index) => (
))}
) export function InfiniteList({ tableName, columns = '*', pageSize = 20, trailingQuery, renderItem, className, renderNoResults = DefaultNoResults, renderEndMessage = DefaultEndMessage, renderSkeleton = defaultSkeleton, }: InfiniteListProps) { const { data, isFetching, hasMore, fetchNextPage, isSuccess } = useInfiniteQuery({ tableName, columns, pageSize, trailingQuery, }) // Ref for the scrolling container const scrollContainerRef = React.useRef(null) // Intersection observer logic - target the last rendered *item* or a dedicated sentinel const loadMoreSentinelRef = React.useRef(null) const observer = React.useRef(null) React.useEffect(() => { if (observer.current) observer.current.disconnect() observer.current = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting && hasMore && !isFetching) { fetchNextPage() } }, { root: scrollContainerRef.current, // Use the scroll container for scroll detection threshold: 0.1, // Trigger when 10% of the target is visible rootMargin: '0px 0px 100px 0px', // Trigger loading a bit before reaching the end } ) if (loadMoreSentinelRef.current) { observer.current.observe(loadMoreSentinelRef.current) } return () => { if (observer.current) observer.current.disconnect() } }, [isFetching, hasMore, fetchNextPage]) return (
{isSuccess && data.length === 0 && renderNoResults()} {data.map((item, index) => renderItem(item, index))} {isFetching && renderSkeleton && renderSkeleton(pageSize)}
{!hasMore && data.length > 0 && renderEndMessage()}
) } ``` -------------------------------- ### Verify Email OTP in Next.js Route Handler Source: https://supabase.com/ui/docs/nextjs/password-based-auth This route handler verifies the email OTP token and redirects the user. Ensure the `createClient` function is correctly set up in your project. ```typescript import { type EmailOtpType } from '@supabase/supabase-js' import { redirect } from 'next/navigation' import { type NextRequest } from 'next/server' import { createClient } from '@/lib/supabase/server' export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url) const token_hash = searchParams.get('token_hash') const type = searchParams.get('type') as EmailOtpType | null const _next = searchParams.get('next') const next = _next?.startsWith('/') ? _next : '/' if (token_hash && type) { const supabase = await createClient() const { error } = await supabase.auth.verifyOtp({ type, token_hash, }) if (!error) { // redirect user to specified redirect URL or root of app redirect(next) } else { // redirect the user to an error page with some instructions redirect(`/auth/error?error=${error?.message}`) } } // redirect the user to an error page with some instructions redirect(`/auth/error?error=No token hash or type`) } ``` -------------------------------- ### Dropzone Component Implementation Source: https://supabase.com/ui/docs/nextjs/dropzone A React component for file uploads, integrating with Supabase. It handles drag-and-drop, file validation, and displays upload status. ```typescript 1'use client' 2 3import { CheckCircle, File, Loader2, Upload, X } from 'lucide-react' 4import { createContext, useCallback, useContext, type PropsWithChildren } from 'react' 5 6import { cn } from '@/lib/utils' 7import { type UseSupabaseUploadReturn } from '@/hooks/use-supabase-upload' 8import { Button } from '@/components/ui/button' 9 10export const formatBytes = ( 11 bytes: number, 12 decimals = 2, 13 size?: 'bytes' | 'KB' | 'MB' | 'GB' | 'TB' | 'PB' | 'EB' | 'ZB' | 'YB' 14) => { 15 const k = 1000 16 const dm = decimals < 0 ? 0 : decimals 17 const sizes = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] 18 19 if (bytes === 0 || bytes === undefined) return size !== undefined ? `0 ${size}` : '0 bytes' 20 const i = size !== undefined ? sizes.indexOf(size) : Math.floor(Math.log(bytes) / Math.log(k)) 21 return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i] 22} 23 24type DropzoneContextType = Omit 25 26const DropzoneContext = createContext(undefined) 27 28type DropzoneProps = UseSupabaseUploadReturn & { 29 className?: string 30} 31 32const Dropzone = ({ 33 className, 34 children, 35 getRootProps, 36 getInputProps, 37 ...restProps 38}: PropsWithChildren) => { 39 const isSuccess = restProps.isSuccess 40 const isActive = restProps.isDragActive 41 const isInvalid = 42 (restProps.isDragActive && restProps.isDragReject) || 43 (restProps.errors.length > 0 && !restProps.isSuccess) || 44 restProps.files.some((file) => file.errors.length !== 0) 45 46 return ( 47 48
59 60 {children} 61
62
63 ) 64} 65const DropzoneContent = ({ className }: { className?: string }) => { 66 const { 67 files, 68 setFiles, 69 onUpload, 70 loading, 71 successes, 72 errors, 73 maxFileSize, 74 maxFiles, 75 isSuccess, 76 } = useDropzoneContext() 77 78 const exceedMaxFiles = files.length > maxFiles 79 80 const handleRemoveFile = useCallback( 81 (fileName: string) => { 82 setFiles(files.filter((file) => file.name !== fileName)) 83 }, 84 [files, setFiles] 85 ) 86 87 if (isSuccess) { 88 return ( 89
90 91

92 Successfully uploaded {files.length} file{files.length > 1 ? 's' : ''} 93

94
95 ) 96 } 97 98 return ( 99
100 {files.map((file, idx) => { 101 const fileError = errors.find((e) => e.name === file.name) 102 const isSuccessfullyUploaded = !!successes.find((e) => e === file.name) 103 104 return ( 105
109 {file.type.startsWith('image/') ? ( 110
111 {file.name} 112
113 ) : ( 114
115 116
117 )} 118 119
120

121 {file.name} 122

123 {file.errors.length > 0 ? ( 124

125 {file.errors 126 .map((e) => 127 e.message.startsWith('File is larger than') 128 ? `File is larger than ${formatBytes(maxFileSize, 2)} (Size: ${formatBytes(file.size, 2)})` 129 : e.message 130 ) 131 .join(', ')} 132

133 ) : loading && !isSuccessfullyUploaded ? ( ``` -------------------------------- ### RealtimeMonaco Component Implementation Source: https://supabase.com/ui/docs/nextjs/realtime-monaco The core React component for the Realtime Monaco editor. It uses the `useConnectOnMount` hook to establish connections for persistence and awareness. ```typescript 'use client' import { Editor } from '@monaco-editor/react' import { SupabasePersistenceOptions } from '@supabase-labs/y-supabase' import { Awareness } from 'y-protocols/awareness.js' import { useConnectOnMount } from '../hooks/use-connect-on-mount' type RealtimeMonacoProps = { channel: string language?: string height?: string | number className?: string awareness?: boolean | Awareness persistence?: boolean | SupabasePersistenceOptions theme?: 'light' | 'dark' } const DEFAULT_HEIGHT = 550 const RealtimeMonaco = ({ channel, language = 'javascript', height = DEFAULT_HEIGHT, awareness = true, persistence, theme, ...rest }: RealtimeMonacoProps) => { const { connectOnMount } = useConnectOnMount({ channel, persistence, awareness }) return ( ) } export { RealtimeMonaco } ``` -------------------------------- ### Add Toaster for Notifications in Root Layout Source: https://supabase.com/ui/docs/platform/platform-kit Include the Toaster component in your application's root layout file (e.g., `layout.tsx` or `App.tsx`) to display toast notifications for Supabase-related events. ```javascript import { Toaster } from '@/components/ui/sonner' export default function RootLayout({ children }) { return (
{children}
) } ``` -------------------------------- ### Format Bytes Utility Function Source: https://supabase.com/ui/docs/nextjs/dropzone Converts bytes into a human-readable format (KB, MB, GB, etc.). Useful for displaying file sizes. ```typescript export const formatBytes = ( bytes: number, decimals = 2, size?: 'bytes' | 'KB' | 'MB' | 'GB' | 'TB' | 'PB' | 'EB' | 'ZB' | 'YB' ) => { const k = 1000 const dm = decimals < 0 ? 0 : decimals const sizes = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] if (bytes === 0 || bytes === undefined) return size !== undefined ? `0 ${size}` : '0 bytes' const i = size !== undefined ? sizes.indexOf(size) : Math.floor(Math.log(bytes) / Math.log(k)) return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i] } ``` -------------------------------- ### Embed Supabase Manager Dialog in React App Source: https://supabase.com/ui/docs/platform/platform-kit Use this snippet to embed the Supabase Manager dialog in your React application. Ensure you replace 'your-project-ref' with your actual Supabase project reference. ```javascript import { useState } from 'react' import SupabaseManagerDialog from '@/components/supabase-manager' import { Button } from '@/components/ui/button' import { useMobile } from '@/hooks/use-mobile' export default function Example() { const [open, setOpen] = useState(false) const projectRef = 'your-project-ref' // Replace with your actual project ref const isMobile = useMobile() return ( <> ) } ``` -------------------------------- ### useDropzoneContext Hook Source: https://supabase.com/ui/docs/nextjs/dropzone Custom hook to access the Dropzone context. Throws an error if used outside of a Dropzone component, ensuring proper component hierarchy. ```javascript const useDropzoneContext = () => { const context = useContext(DropzoneContext) if (!context) { throw new Error('useDropzoneContext must be used within a Dropzone') } return context } ``` -------------------------------- ### CurrentUserAvatar Component Source: https://supabase.com/ui/docs/nextjs/current-user-avatar This component fetches and displays the current user's avatar from Supabase Auth. It falls back to showing initials if no image is found. Ensure you have the necessary hooks and UI components imported. ```typescript 'use client' import { useCurrentUserImage } from '@/hooks/use-current-user-image' import { useCurrentUserName } from '@/hooks/use-current-user-name' import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' export const CurrentUserAvatar = () => { const profileImage = useCurrentUserImage() const name = useCurrentUserName() const initials = name ?.split(' ') ?.map((word) => word[0]) ?.join('') ?.toUpperCase() return ( {profileImage && } {initials} ) } ``` -------------------------------- ### Cursor Component for Realtime Cursor Sharing Source: https://supabase.com/ui/docs/nextjs/realtime-cursor This React component displays a user's cursor and name, styled with a specified color. It's designed to be used within a real-time collaboration context. ```typescript import { MousePointer2 } from 'lucide-react' import { cn } from '@/lib/utils' export const Cursor = ({ className, style, color, name, }: { className?: string style?: React.CSSProperties color: string name: string }) => { return (
{name}
) } ```