@fluxbase/sdk-react 0.0.1-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/context.tsx","../src/use-auth.ts","../src/use-query.ts","../src/use-realtime.ts","../src/use-storage.ts","../src/use-rpc.ts","../src/use-admin-auth.ts","../src/use-users.ts","../src/use-api-keys.ts","../src/use-admin-hooks.ts"],"sourcesContent":["/**\n * React context for Fluxbase client\n */\n\nimport { createContext, useContext, type ReactNode } from 'react'\nimport type { FluxbaseClient } from '@fluxbase/sdk'\n\nconst FluxbaseContext = createContext<FluxbaseClient | null>(null)\n\nexport interface FluxbaseProviderProps {\n client: FluxbaseClient\n children: ReactNode\n}\n\n/**\n * Provider component to make Fluxbase client available throughout the app\n */\nexport function FluxbaseProvider({ client, children }: FluxbaseProviderProps) {\n return <FluxbaseContext.Provider value={client}>{children}</FluxbaseContext.Provider>\n}\n\n/**\n * Hook to access the Fluxbase client from context\n */\nexport function useFluxbaseClient(): FluxbaseClient {\n const client = useContext(FluxbaseContext)\n\n if (!client) {\n throw new Error('useFluxbaseClient must be used within a FluxbaseProvider')\n }\n\n return client\n}\n","/**\n * Authentication hooks for Fluxbase SDK\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'\nimport { useFluxbaseClient } from './context'\nimport type { SignInCredentials, SignUpCredentials, User, AuthSession } from '@fluxbase/sdk'\n\n/**\n * Hook to get the current user\n */\nexport function useUser() {\n const client = useFluxbaseClient()\n\n return useQuery({\n queryKey: ['fluxbase', 'auth', 'user'],\n queryFn: async () => {\n const session = client.auth.getSession()\n if (!session) {\n return null\n }\n\n try {\n return await client.auth.getCurrentUser()\n } catch {\n return null\n }\n },\n staleTime: 1000 * 60 * 5, // 5 minutes\n })\n}\n\n/**\n * Hook to get the current session\n */\nexport function useSession() {\n const client = useFluxbaseClient()\n\n return useQuery<AuthSession | null>({\n queryKey: ['fluxbase', 'auth', 'session'],\n queryFn: () => client.auth.getSession(),\n staleTime: 1000 * 60 * 5, // 5 minutes\n })\n}\n\n/**\n * Hook for signing in\n */\nexport function useSignIn() {\n const client = useFluxbaseClient()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: async (credentials: SignInCredentials) => {\n return await client.auth.signIn(credentials)\n },\n onSuccess: (session) => {\n queryClient.setQueryData(['fluxbase', 'auth', 'session'], session)\n // Only set user if this is a complete auth session (not 2FA required)\n if ('user' in session) {\n queryClient.setQueryData(['fluxbase', 'auth', 'user'], session.user)\n }\n },\n })\n}\n\n/**\n * Hook for signing up\n */\nexport function useSignUp() {\n const client = useFluxbaseClient()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: async (credentials: SignUpCredentials) => {\n return await client.auth.signUp(credentials)\n },\n onSuccess: (session) => {\n queryClient.setQueryData(['fluxbase', 'auth', 'session'], session)\n queryClient.setQueryData(['fluxbase', 'auth', 'user'], session.user)\n },\n })\n}\n\n/**\n * Hook for signing out\n */\nexport function useSignOut() {\n const client = useFluxbaseClient()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: async () => {\n await client.auth.signOut()\n },\n onSuccess: () => {\n queryClient.setQueryData(['fluxbase', 'auth', 'session'], null)\n queryClient.setQueryData(['fluxbase', 'auth', 'user'], null)\n queryClient.invalidateQueries({ queryKey: ['fluxbase'] })\n },\n })\n}\n\n/**\n * Hook for updating the current user\n */\nexport function useUpdateUser() {\n const client = useFluxbaseClient()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: async (data: Partial<Pick<User, 'email' | 'metadata'>>) => {\n return await client.auth.updateUser(data)\n },\n onSuccess: (user) => {\n queryClient.setQueryData(['fluxbase', 'auth', 'user'], user)\n },\n })\n}\n\n/**\n * Combined auth hook with all auth state and methods\n */\nexport function useAuth() {\n const { data: user, isLoading: isLoadingUser } = useUser()\n const { data: session, isLoading: isLoadingSession } = useSession()\n const signIn = useSignIn()\n const signUp = useSignUp()\n const signOut = useSignOut()\n const updateUser = useUpdateUser()\n\n return {\n user,\n session,\n isLoading: isLoadingUser || isLoadingSession,\n isAuthenticated: !!session,\n signIn: signIn.mutateAsync,\n signUp: signUp.mutateAsync,\n signOut: signOut.mutateAsync,\n updateUser: updateUser.mutateAsync,\n isSigningIn: signIn.isPending,\n isSigningUp: signUp.isPending,\n isSigningOut: signOut.isPending,\n isUpdating: updateUser.isPending,\n }\n}\n","/**\n * Database query hooks for Fluxbase SDK\n */\n\nimport { useQuery, useMutation, useQueryClient, type UseQueryOptions } from '@tanstack/react-query'\nimport { useFluxbaseClient } from './context'\nimport type { QueryBuilder } from '@fluxbase/sdk'\n\nexport interface UseFluxbaseQueryOptions<T> extends Omit<UseQueryOptions<T[], Error>, 'queryKey' | 'queryFn'> {\n /**\n * Custom query key. If not provided, will use table name and filters.\n */\n queryKey?: unknown[]\n}\n\n/**\n * Hook to execute a database query\n * @param buildQuery - Function that builds and returns the query\n * @param options - React Query options\n */\nexport function useFluxbaseQuery<T = any>(\n buildQuery: (client: ReturnType<typeof useFluxbaseClient>) => QueryBuilder<T>,\n options?: UseFluxbaseQueryOptions<T>\n) {\n const client = useFluxbaseClient()\n\n // Build a stable query key\n const queryKey = options?.queryKey || ['fluxbase', 'query', buildQuery.toString()]\n\n return useQuery({\n queryKey,\n queryFn: async () => {\n const query = buildQuery(client)\n const { data, error } = await query.execute()\n\n if (error) {\n throw error\n }\n\n return (Array.isArray(data) ? data : data ? [data] : []) as T[]\n },\n ...options,\n })\n}\n\n/**\n * Hook for table queries with a simpler API\n * @param table - Table name\n * @param buildQuery - Function to build the query\n */\nexport function useTable<T = any>(\n table: string,\n buildQuery?: (query: QueryBuilder<T>) => QueryBuilder<T>,\n options?: UseFluxbaseQueryOptions<T>\n) {\n const client = useFluxbaseClient()\n\n return useFluxbaseQuery(\n (client) => {\n const query = client.from<T>(table)\n return buildQuery ? buildQuery(query) : query\n },\n {\n ...options,\n queryKey: options?.queryKey || ['fluxbase', 'table', table, buildQuery?.toString()],\n }\n )\n}\n\n/**\n * Hook to insert data into a table\n */\nexport function useInsert<T = any>(table: string) {\n const client = useFluxbaseClient()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: async (data: Partial<T> | Partial<T>[]) => {\n const query = client.from<T>(table)\n const { data: result, error } = await query.insert(data as Partial<T>)\n\n if (error) {\n throw error\n }\n\n return result\n },\n onSuccess: () => {\n // Invalidate all queries for this table\n queryClient.invalidateQueries({ queryKey: ['fluxbase', 'table', table] })\n },\n })\n}\n\n/**\n * Hook to update data in a table\n */\nexport function useUpdate<T = any>(table: string) {\n const client = useFluxbaseClient()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: async (params: { data: Partial<T>; buildQuery: (query: QueryBuilder<T>) => QueryBuilder<T> }) => {\n const query = client.from<T>(table)\n const builtQuery = params.buildQuery(query)\n const { data: result, error } = await builtQuery.update(params.data)\n\n if (error) {\n throw error\n }\n\n return result\n },\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['fluxbase', 'table', table] })\n },\n })\n}\n\n/**\n * Hook to upsert data into a table\n */\nexport function useUpsert<T = any>(table: string) {\n const client = useFluxbaseClient()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: async (data: Partial<T> | Partial<T>[]) => {\n const query = client.from<T>(table)\n const { data: result, error } = await query.upsert(data as Partial<T>)\n\n if (error) {\n throw error\n }\n\n return result\n },\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['fluxbase', 'table', table] })\n },\n })\n}\n\n/**\n * Hook to delete data from a table\n */\nexport function useDelete<T = any>(table: string) {\n const client = useFluxbaseClient()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: async (buildQuery: (query: QueryBuilder<T>) => QueryBuilder<T>) => {\n const query = client.from<T>(table)\n const builtQuery = buildQuery(query)\n const { error } = await builtQuery.delete()\n\n if (error) {\n throw error\n }\n },\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['fluxbase', 'table', table] })\n },\n })\n}\n","/**\n * Realtime subscription hooks for Fluxbase SDK\n */\n\nimport { useEffect, useRef } from 'react'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { useFluxbaseClient } from './context'\nimport type { RealtimeCallback, RealtimeChangePayload } from '@fluxbase/sdk'\n\nexport interface UseRealtimeOptions {\n /**\n * The channel name (e.g., 'table:public.products')\n */\n channel: string\n\n /**\n * Event type to listen for ('INSERT', 'UPDATE', 'DELETE', or '*' for all)\n */\n event?: 'INSERT' | 'UPDATE' | 'DELETE' | '*'\n\n /**\n * Callback function when an event is received\n */\n callback?: RealtimeCallback\n\n /**\n * Whether to automatically invalidate queries for the table\n * Default: true\n */\n autoInvalidate?: boolean\n\n /**\n * Custom query key to invalidate (if autoInvalidate is true)\n * Default: ['fluxbase', 'table', tableName]\n */\n invalidateKey?: unknown[]\n\n /**\n * Whether the subscription is enabled\n * Default: true\n */\n enabled?: boolean\n}\n\n/**\n * Hook to subscribe to realtime changes for a channel\n */\nexport function useRealtime(options: UseRealtimeOptions) {\n const client = useFluxbaseClient()\n const queryClient = useQueryClient()\n const channelRef = useRef<ReturnType<typeof client.realtime.channel> | null>(null)\n\n const {\n channel: channelName,\n event = '*',\n callback,\n autoInvalidate = true,\n invalidateKey,\n enabled = true,\n } = options\n\n useEffect(() => {\n if (!enabled) {\n return\n }\n\n // Create channel and subscribe\n const channel = client.realtime.channel(channelName)\n channelRef.current = channel\n\n const handleChange = (payload: RealtimeChangePayload) => {\n // Call user callback\n if (callback) {\n callback(payload)\n }\n\n // Auto-invalidate queries if enabled\n if (autoInvalidate) {\n // Extract table name from channel (e.g., 'table:public.products' -> 'public.products')\n const tableName = channelName.replace(/^table:/, '')\n\n const key = invalidateKey || ['fluxbase', 'table', tableName]\n queryClient.invalidateQueries({ queryKey: key })\n }\n }\n\n channel.on(event, handleChange).subscribe()\n\n return () => {\n channel.unsubscribe()\n channelRef.current = null\n }\n }, [client, channelName, event, callback, autoInvalidate, invalidateKey, queryClient, enabled])\n\n return {\n channel: channelRef.current,\n }\n}\n\n/**\n * Hook to subscribe to a table's changes\n * @param table - Table name (with optional schema, e.g., 'public.products')\n * @param options - Subscription options\n */\nexport function useTableSubscription(\n table: string,\n options?: Omit<UseRealtimeOptions, 'channel'>\n) {\n return useRealtime({\n ...options,\n channel: `table:${table}`,\n })\n}\n\n/**\n * Hook to subscribe to INSERT events on a table\n */\nexport function useTableInserts(\n table: string,\n callback: (payload: RealtimeChangePayload) => void,\n options?: Omit<UseRealtimeOptions, 'channel' | 'event' | 'callback'>\n) {\n return useRealtime({\n ...options,\n channel: `table:${table}`,\n event: 'INSERT',\n callback,\n })\n}\n\n/**\n * Hook to subscribe to UPDATE events on a table\n */\nexport function useTableUpdates(\n table: string,\n callback: (payload: RealtimeChangePayload) => void,\n options?: Omit<UseRealtimeOptions, 'channel' | 'event' | 'callback'>\n) {\n return useRealtime({\n ...options,\n channel: `table:${table}`,\n event: 'UPDATE',\n callback,\n })\n}\n\n/**\n * Hook to subscribe to DELETE events on a table\n */\nexport function useTableDeletes(\n table: string,\n callback: (payload: RealtimeChangePayload) => void,\n options?: Omit<UseRealtimeOptions, 'channel' | 'event' | 'callback'>\n) {\n return useRealtime({\n ...options,\n channel: `table:${table}`,\n event: 'DELETE',\n callback,\n })\n}\n","/**\n * Storage hooks for Fluxbase SDK\n */\n\nimport { useMutation, useQuery, useQueryClient, type UseQueryOptions } from '@tanstack/react-query'\nimport { useFluxbaseClient } from './context'\nimport type { ListOptions, UploadOptions } from '@fluxbase/sdk'\n\n/**\n * Hook to list files in a bucket\n */\nexport function useStorageList(\n bucket: string,\n options?: ListOptions & Omit<UseQueryOptions<any[], Error>, 'queryKey' | 'queryFn'>\n) {\n const client = useFluxbaseClient()\n const { prefix, limit, offset, ...queryOptions } = options || {}\n\n return useQuery({\n queryKey: ['fluxbase', 'storage', bucket, 'list', { prefix, limit, offset }],\n queryFn: async () => {\n const { data, error } = await client.storage.from(bucket).list({ prefix, limit, offset })\n\n if (error) {\n throw error\n }\n\n return data || []\n },\n ...queryOptions,\n })\n}\n\n/**\n * Hook to upload a file to a bucket\n */\nexport function useStorageUpload(bucket: string) {\n const client = useFluxbaseClient()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: async (params: {\n path: string\n file: File | Blob | ArrayBuffer\n options?: UploadOptions\n }) => {\n const { path, file, options } = params\n const { data, error } = await client.storage.from(bucket).upload(path, file, options)\n\n if (error) {\n throw error\n }\n\n return data\n },\n onSuccess: () => {\n // Invalidate list queries for this bucket\n queryClient.invalidateQueries({ queryKey: ['fluxbase', 'storage', bucket, 'list'] })\n },\n })\n}\n\n/**\n * Hook to download a file from a bucket\n */\nexport function useStorageDownload(bucket: string, path: string | null, enabled = true) {\n const client = useFluxbaseClient()\n\n return useQuery({\n queryKey: ['fluxbase', 'storage', bucket, 'download', path],\n queryFn: async () => {\n if (!path) {\n return null\n }\n\n const { data, error } = await client.storage.from(bucket).download(path)\n\n if (error) {\n throw error\n }\n\n return data\n },\n enabled: enabled && !!path,\n })\n}\n\n/**\n * Hook to delete files from a bucket\n */\nexport function useStorageDelete(bucket: string) {\n const client = useFluxbaseClient()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: async (paths: string[]) => {\n const { error } = await client.storage.from(bucket).remove(paths)\n\n if (error) {\n throw error\n }\n },\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['fluxbase', 'storage', bucket, 'list'] })\n },\n })\n}\n\n/**\n * Hook to get a public URL for a file\n */\nexport function useStoragePublicUrl(bucket: string, path: string | null) {\n const client = useFluxbaseClient()\n\n if (!path) {\n return null\n }\n\n const { data } = client.storage.from(bucket).getPublicUrl(path)\n return data.publicUrl\n}\n\n/**\n * Hook to create a signed URL\n */\nexport function useStorageSignedUrl(bucket: string, path: string | null, expiresIn?: number) {\n const client = useFluxbaseClient()\n\n return useQuery({\n queryKey: ['fluxbase', 'storage', bucket, 'signed-url', path, expiresIn],\n queryFn: async () => {\n if (!path) {\n return null\n }\n\n const { data, error } = await client.storage.from(bucket).createSignedUrl(path, { expiresIn })\n\n if (error) {\n throw error\n }\n\n return data?.signedUrl || null\n },\n enabled: !!path,\n staleTime: expiresIn ? expiresIn * 1000 - 60000 : 1000 * 60 * 50, // Refresh 1 minute before expiry\n })\n}\n\n/**\n * Hook to move a file\n */\nexport function useStorageMove(bucket: string) {\n const client = useFluxbaseClient()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: async (params: { fromPath: string; toPath: string }) => {\n const { fromPath, toPath } = params\n const { data, error } = await client.storage.from(bucket).move(fromPath, toPath)\n\n if (error) {\n throw error\n }\n\n return data\n },\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['fluxbase', 'storage', bucket, 'list'] })\n },\n })\n}\n\n/**\n * Hook to copy a file\n */\nexport function useStorageCopy(bucket: string) {\n const client = useFluxbaseClient()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: async (params: { fromPath: string; toPath: string }) => {\n const { fromPath, toPath } = params\n const { data, error } = await client.storage.from(bucket).copy(fromPath, toPath)\n\n if (error) {\n throw error\n }\n\n return data\n },\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['fluxbase', 'storage', bucket, 'list'] })\n },\n })\n}\n\n/**\n * Hook to manage buckets\n */\nexport function useStorageBuckets() {\n const client = useFluxbaseClient()\n\n return useQuery({\n queryKey: ['fluxbase', 'storage', 'buckets'],\n queryFn: async () => {\n const { data, error } = await client.storage.listBuckets()\n\n if (error) {\n throw error\n }\n\n return data || []\n },\n })\n}\n\n/**\n * Hook to create a bucket\n */\nexport function useCreateBucket() {\n const client = useFluxbaseClient()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: async (bucketName: string) => {\n const { error } = await client.storage.createBucket(bucketName)\n\n if (error) {\n throw error\n }\n },\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['fluxbase', 'storage', 'buckets'] })\n },\n })\n}\n\n/**\n * Hook to delete a bucket\n */\nexport function useDeleteBucket() {\n const client = useFluxbaseClient()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: async (bucketName: string) => {\n const { error } = await client.storage.deleteBucket(bucketName)\n\n if (error) {\n throw error\n }\n },\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['fluxbase', 'storage', 'buckets'] })\n },\n })\n}\n","/**\n * React hooks for RPC (Remote Procedure Calls)\n * Call PostgreSQL functions with React Query integration\n */\n\nimport { useQuery, useMutation, useQueryClient, type UseQueryOptions, type UseMutationOptions } from '@tanstack/react-query'\nimport { useFluxbaseClient } from './context'\nimport type { PostgrestResponse } from '@fluxbase/sdk'\n\n/**\n * Hook to call a PostgreSQL function and cache the result\n *\n * @example\n * ```tsx\n * const { data, isLoading, error } = useRPC(\n * 'calculate_total',\n * { order_id: 123 },\n * { enabled: !!orderId }\n * )\n * ```\n */\nexport function useRPC<TData = unknown, TParams extends Record<string, unknown> = Record<string, unknown>>(\n functionName: string,\n params?: TParams,\n options?: Omit<UseQueryOptions<TData, Error>, 'queryKey' | 'queryFn'>\n) {\n const client = useFluxbaseClient()\n\n return useQuery<TData, Error>({\n queryKey: ['rpc', functionName, params],\n queryFn: async () => {\n const { data, error } = await client.rpc<TData>(functionName, params)\n if (error) {\n throw new Error(error.message)\n }\n return data as TData\n },\n ...options,\n })\n}\n\n/**\n * Hook to create a mutation for calling PostgreSQL functions\n * Useful for functions that modify data\n *\n * @example\n * ```tsx\n * const createOrder = useRPCMutation('create_order')\n *\n * const handleSubmit = async () => {\n * await createOrder.mutateAsync({\n * user_id: 123,\n * items: [{ product_id: 1, quantity: 2 }]\n * })\n * }\n * ```\n */\nexport function useRPCMutation<TData = unknown, TParams extends Record<string, unknown> = Record<string, unknown>>(\n functionName: string,\n options?: Omit<UseMutationOptions<TData, Error, TParams>, 'mutationFn'>\n) {\n const client = useFluxbaseClient()\n\n return useMutation<TData, Error, TParams>({\n mutationFn: async (params: TParams) => {\n const { data, error } = await client.rpc<TData>(functionName, params)\n if (error) {\n throw new Error(error.message)\n }\n return data as TData\n },\n ...options,\n })\n}\n\n/**\n * Hook to call multiple RPC functions in parallel\n *\n * @example\n * ```tsx\n * const { data, isLoading } = useRPCBatch([\n * { name: 'get_user_stats', params: { user_id: 123 } },\n * { name: 'get_recent_orders', params: { limit: 10 } },\n * ])\n * ```\n */\nexport function useRPCBatch<TData = unknown>(\n calls: Array<{ name: string; params?: Record<string, unknown> }>,\n options?: Omit<UseQueryOptions<TData[], Error, TData[], readonly unknown[]>, 'queryKey' | 'queryFn'>\n) {\n const client = useFluxbaseClient()\n\n return useQuery({\n queryKey: ['rpc-batch', calls] as const,\n queryFn: async () => {\n const results = await Promise.all(\n calls.map(async ({ name, params }) => {\n const { data, error } = await client.rpc<TData>(name, params)\n if (error) {\n throw new Error(`${name}: ${error.message}`)\n }\n return data\n })\n )\n return results as TData[]\n },\n ...options,\n })\n}\n","import { useState, useEffect, useCallback } from 'react'\nimport { useFluxbaseClient } from './context'\nimport type { AdminAuthResponse } from '@fluxbase/sdk'\n\n/**\n * Simplified admin user type returned by authentication\n */\nexport interface AdminUser {\n id: string\n email: string\n role: string\n}\n\nexport interface UseAdminAuthOptions {\n /**\n * Automatically check authentication status on mount\n * @default true\n */\n autoCheck?: boolean\n}\n\nexport interface UseAdminAuthReturn {\n /**\n * Current admin user if authenticated\n */\n user: AdminUser | null\n\n /**\n * Whether the admin is authenticated\n */\n isAuthenticated: boolean\n\n /**\n * Whether the authentication check is in progress\n */\n isLoading: boolean\n\n /**\n * Any error that occurred during authentication\n */\n error: Error | null\n\n /**\n * Login as admin\n */\n login: (email: string, password: string) => Promise<AdminAuthResponse>\n\n /**\n * Logout admin\n */\n logout: () => Promise<void>\n\n /**\n * Refresh admin user info\n */\n refresh: () => Promise<void>\n}\n\n/**\n * Hook for admin authentication\n *\n * Manages admin login state, authentication checks, and user info.\n *\n * @example\n * ```tsx\n * function AdminLogin() {\n * const { user, isAuthenticated, isLoading, login, logout } = useAdminAuth()\n *\n * const handleLogin = async (e: React.FormEvent) => {\n * e.preventDefault()\n * await login(email, password)\n * }\n *\n * if (isLoading) return <div>Loading...</div>\n * if (isAuthenticated) return <div>Welcome {user?.email}</div>\n *\n * return <form onSubmit={handleLogin}>...</form>\n * }\n * ```\n */\nexport function useAdminAuth(options: UseAdminAuthOptions = {}): UseAdminAuthReturn {\n const { autoCheck = true } = options\n const client = useFluxbaseClient()\n\n const [user, setUser] = useState<AdminUser | null>(null)\n const [isLoading, setIsLoading] = useState(autoCheck)\n const [error, setError] = useState<Error | null>(null)\n\n /**\n * Check current authentication status\n */\n const checkAuth = useCallback(async () => {\n try {\n setIsLoading(true)\n setError(null)\n const { user } = await client.admin.me()\n setUser(user)\n } catch (err) {\n setUser(null)\n setError(err as Error)\n } finally {\n setIsLoading(false)\n }\n }, [client])\n\n /**\n * Login as admin\n */\n const login = useCallback(\n async (email: string, password: string): Promise<AdminAuthResponse> => {\n try {\n setIsLoading(true)\n setError(null)\n const response = await client.admin.login({ email, password })\n setUser(response.user)\n return response\n } catch (err) {\n setError(err as Error)\n throw err\n } finally {\n setIsLoading(false)\n }\n },\n [client]\n )\n\n /**\n * Logout admin\n */\n const logout = useCallback(async (): Promise<void> => {\n try {\n setIsLoading(true)\n setError(null)\n // Clear user state\n setUser(null)\n // Note: Add logout endpoint call here when available\n } catch (err) {\n setError(err as Error)\n throw err\n } finally {\n setIsLoading(false)\n }\n }, [])\n\n /**\n * Refresh admin user info\n */\n const refresh = useCallback(async (): Promise<void> => {\n await checkAuth()\n }, [checkAuth])\n\n // Auto-check authentication on mount\n useEffect(() => {\n if (autoCheck) {\n checkAuth()\n }\n }, [autoCheck, checkAuth])\n\n return {\n user,\n isAuthenticated: user !== null,\n isLoading,\n error,\n login,\n logout,\n refresh\n }\n}\n","import { useState, useEffect, useCallback } from 'react'\nimport { useFluxbaseClient } from './context'\nimport type { EnrichedUser, ListUsersOptions } from '@fluxbase/sdk'\n\nexport interface UseUsersOptions extends ListUsersOptions {\n /**\n * Whether to automatically fetch users on mount\n * @default true\n */\n autoFetch?: boolean\n\n /**\n * Refetch interval in milliseconds (0 to disable)\n * @default 0\n */\n refetchInterval?: number\n}\n\nexport interface UseUsersReturn {\n /**\n * Array of users\n */\n users: EnrichedUser[]\n\n /**\n * Total number of users (for pagination)\n */\n total: number\n\n /**\n * Whether users are being fetched\n */\n isLoading: boolean\n\n /**\n * Any error that occurred\n */\n error: Error | null\n\n /**\n * Refetch users\n */\n refetch: () => Promise<void>\n\n /**\n * Invite a new user\n */\n inviteUser: (email: string, role: 'user' | 'admin') => Promise<void>\n\n /**\n * Update user role\n */\n updateUserRole: (userId: string, role: 'user' | 'admin') => Promise<void>\n\n /**\n * Delete a user\n */\n deleteUser: (userId: string) => Promise<void>\n\n /**\n * Reset user password\n */\n resetPassword: (userId: string) => Promise<{ message: string }>\n}\n\n/**\n * Hook for managing users\n *\n * Provides user list with pagination, search, and management functions.\n *\n * @example\n * ```tsx\n * function UserList() {\n * const { users, total, isLoading, refetch, inviteUser, deleteUser } = useUsers({\n * limit: 20,\n * search: searchTerm\n * })\n *\n * return (\n * <div>\n * {isLoading ? <Spinner /> : (\n * <ul>\n * {users.map(user => (\n * <li key={user.id}>\n * {user.email} - {user.role}\n * <button onClick={() => deleteUser(user.id)}>Delete</button>\n * </li>\n * ))}\n * </ul>\n * )}\n * </div>\n * )\n * }\n * ```\n */\nexport function useUsers(options: UseUsersOptions = {}): UseUsersReturn {\n const { autoFetch = true, refetchInterval = 0, ...listOptions } = options\n const client = useFluxbaseClient()\n\n const [users, setUsers] = useState<EnrichedUser[]>([])\n const [total, setTotal] = useState(0)\n const [isLoading, setIsLoading] = useState(autoFetch)\n const [error, setError] = useState<Error | null>(null)\n\n /**\n * Fetch users from API\n */\n const fetchUsers = useCallback(async () => {\n try {\n setIsLoading(true)\n setError(null)\n const response = await client.admin.listUsers(listOptions)\n setUsers(response.users)\n setTotal(response.total)\n } catch (err) {\n setError(err as Error)\n } finally {\n setIsLoading(false)\n }\n }, [client, JSON.stringify(listOptions)])\n\n /**\n * Invite a new user\n */\n const inviteUser = useCallback(\n async (email: string, role: 'user' | 'admin'): Promise<void> => {\n await client.admin.inviteUser({ email, role })\n await fetchUsers() // Refresh list\n },\n [client, fetchUsers]\n )\n\n /**\n * Update user role\n */\n const updateUserRole = useCallback(\n async (userId: string, role: 'user' | 'admin'): Promise<void> => {\n await client.admin.updateUserRole(userId, role)\n await fetchUsers() // Refresh list\n },\n [client, fetchUsers]\n )\n\n /**\n * Delete a user\n */\n const deleteUser = useCallback(\n async (userId: string): Promise<void> => {\n await client.admin.deleteUser(userId)\n await fetchUsers() // Refresh list\n },\n [client, fetchUsers]\n )\n\n /**\n * Reset user password\n */\n const resetPassword = useCallback(\n async (userId: string): Promise<{ message: string }> => {\n return await client.admin.resetUserPassword(userId)\n },\n [client]\n )\n\n // Auto-fetch on mount\n useEffect(() => {\n if (autoFetch) {\n fetchUsers()\n }\n }, [autoFetch, fetchUsers])\n\n // Set up refetch interval\n useEffect(() => {\n if (refetchInterval > 0) {\n const interval = setInterval(fetchUsers, refetchInterval)\n return () => clearInterval(interval)\n }\n }, [refetchInterval, fetchUsers])\n\n return {\n users,\n total,\n isLoading,\n error,\n refetch: fetchUsers,\n inviteUser,\n updateUserRole,\n deleteUser,\n resetPassword\n }\n}\n","import { useState, useEffect, useCallback } from 'react'\nimport { useFluxbaseClient } from './context'\nimport type { APIKey, CreateAPIKeyRequest } from '@fluxbase/sdk'\n\nexport interface UseAPIKeysOptions {\n /**\n * Whether to automatically fetch API keys on mount\n * @default true\n */\n autoFetch?: boolean\n}\n\nexport interface UseAPIKeysReturn {\n /**\n * Array of API keys\n */\n keys: APIKey[]\n\n /**\n * Whether keys are being fetched\n */\n isLoading: boolean\n\n /**\n * Any error that occurred\n */\n error: Error | null\n\n /**\n * Refetch API keys\n */\n refetch: () => Promise<void>\n\n /**\n * Create a new API key\n */\n createKey: (request: CreateAPIKeyRequest) => Promise<{ key: string; keyData: APIKey }>\n\n /**\n * Update an API key\n */\n updateKey: (keyId: string, update: { name?: string; description?: string }) => Promise<void>\n\n /**\n * Revoke an API key\n */\n revokeKey: (keyId: string) => Promise<void>\n\n /**\n * Delete an API key\n */\n deleteKey: (keyId: string) => Promise<void>\n}\n\n/**\n * Hook for managing API keys\n *\n * Provides API key list and management functions.\n *\n * @example\n * ```tsx\n * function APIKeyManager() {\n * const { keys, isLoading, createKey, revokeKey } = useAPIKeys()\n *\n * const handleCreate = async () => {\n * const { key, keyData } = await createKey({\n * name: 'Backend Service',\n * description: 'API key for backend',\n * expires_at: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString()\n * })\n * alert(`Key created: ${key}`)\n * }\n *\n * return (\n * <div>\n * <button onClick={handleCreate}>Create Key</button>\n * {keys.map(k => (\n * <div key={k.id}>\n * {k.name}\n * <button onClick={() => revokeKey(k.id)}>Revoke</button>\n * </div>\n * ))}\n * </div>\n * )\n * }\n * ```\n */\nexport function useAPIKeys(options: UseAPIKeysOptions = {}): UseAPIKeysReturn {\n const { autoFetch = true } = options\n const client = useFluxbaseClient()\n\n const [keys, setKeys] = useState<APIKey[]>([])\n const [isLoading, setIsLoading] = useState(autoFetch)\n const [error, setError] = useState<Error | null>(null)\n\n /**\n * Fetch API keys from API\n */\n const fetchKeys = useCallback(async () => {\n try {\n setIsLoading(true)\n setError(null)\n const response = await client.admin.management.apiKeys.list()\n setKeys(response.api_keys)\n } catch (err) {\n setError(err as Error)\n } finally {\n setIsLoading(false)\n }\n }, [client])\n\n /**\n * Create a new API key\n */\n const createKey = useCallback(\n async (request: CreateAPIKeyRequest): Promise<{ key: string; keyData: APIKey }> => {\n const response = await client.admin.management.apiKeys.create(request)\n await fetchKeys() // Refresh list\n return { key: response.key, keyData: response.api_key }\n },\n [client, fetchKeys]\n )\n\n /**\n * Update an API key\n */\n const updateKey = useCallback(\n async (keyId: string, update: { name?: string; description?: string }): Promise<void> => {\n await client.admin.management.apiKeys.update(keyId, update)\n await fetchKeys() // Refresh list\n },\n [client, fetchKeys]\n )\n\n /**\n * Revoke an API key\n */\n const revokeKey = useCallback(\n async (keyId: string): Promise<void> => {\n await client.admin.management.apiKeys.revoke(keyId)\n await fetchKeys() // Refresh list\n },\n [client, fetchKeys]\n )\n\n /**\n * Delete an API key\n */\n const deleteKey = useCallback(\n async (keyId: string): Promise<void> => {\n await client.admin.management.apiKeys.delete(keyId)\n await fetchKeys() // Refresh list\n },\n [client, fetchKeys]\n )\n\n // Auto-fetch on mount\n useEffect(() => {\n if (autoFetch) {\n fetchKeys()\n }\n }, [autoFetch, fetchKeys])\n\n return {\n keys,\n isLoading,\n error,\n refetch: fetchKeys,\n createKey,\n updateKey,\n revokeKey,\n deleteKey\n }\n}\n","/**\n * Admin Settings and Management Hooks\n *\n * Hooks for managing application settings, system settings, and webhooks.\n */\n\nimport { useState, useEffect, useCallback } from 'react'\nimport { useFluxbaseClient } from './context'\nimport type {\n AppSettings,\n UpdateAppSettingsRequest,\n SystemSetting,\n UpdateSystemSettingRequest,\n Webhook,\n CreateWebhookRequest,\n UpdateWebhookRequest\n} from '@fluxbase/sdk'\n\n// ============================================================================\n// useAppSettings Hook\n// ============================================================================\n\nexport interface UseAppSettingsOptions {\n autoFetch?: boolean\n}\n\nexport interface UseAppSettingsReturn {\n settings: AppSettings | null\n isLoading: boolean\n error: Error | null\n refetch: () => Promise<void>\n updateSettings: (update: UpdateAppSettingsRequest) => Promise<void>\n}\n\n/**\n * Hook for managing application settings\n *\n * @example\n * ```tsx\n * function SettingsPanel() {\n * const { settings, isLoading, updateSettings } = useAppSettings({ autoFetch: true })\n *\n * const handleToggleFeature = async (feature: string, enabled: boolean) => {\n * await updateSettings({\n * features: { ...settings?.features, [feature]: enabled }\n * })\n * }\n *\n * return <div>...</div>\n * }\n * ```\n */\nexport function useAppSettings(options: UseAppSettingsOptions = {}): UseAppSettingsReturn {\n const { autoFetch = true } = options\n const client = useFluxbaseClient()\n\n const [settings, setSettings] = useState<AppSettings | null>(null)\n const [isLoading, setIsLoading] = useState(autoFetch)\n const [error, setError] = useState<Error | null>(null)\n\n const fetchSettings = useCallback(async () => {\n try {\n setIsLoading(true)\n setError(null)\n const appSettings = await client.admin.settings.app.get()\n setSettings(appSettings)\n } catch (err) {\n setError(err as Error)\n } finally {\n setIsLoading(false)\n }\n }, [client])\n\n const updateSettings = useCallback(\n async (update: UpdateAppSettingsRequest): Promise<void> => {\n await client.admin.settings.app.update(update)\n await fetchSettings()\n },\n [client, fetchSettings]\n )\n\n useEffect(() => {\n if (autoFetch) {\n fetchSettings()\n }\n }, [autoFetch, fetchSettings])\n\n return {\n settings,\n isLoading,\n error,\n refetch: fetchSettings,\n updateSettings\n }\n}\n\n// ============================================================================\n// useSystemSettings Hook\n// ============================================================================\n\nexport interface UseSystemSettingsOptions {\n autoFetch?: boolean\n}\n\nexport interface UseSystemSettingsReturn {\n settings: SystemSetting[]\n isLoading: boolean\n error: Error | null\n refetch: () => Promise<void>\n getSetting: (key: string) => SystemSetting | undefined\n updateSetting: (key: string, update: UpdateSystemSettingRequest) => Promise<void>\n deleteSetting: (key: string) => Promise<void>\n}\n\n/**\n * Hook for managing system settings (key-value storage)\n *\n * @example\n * ```tsx\n * function SystemSettings() {\n * const { settings, isLoading, updateSetting } = useSystemSettings({ autoFetch: true })\n *\n * const handleUpdateSetting = async (key: string, value: any) => {\n * await updateSetting(key, { value })\n * }\n *\n * return <div>...</div>\n * }\n * ```\n */\nexport function useSystemSettings(options: UseSystemSettingsOptions = {}): UseSystemSettingsReturn {\n const { autoFetch = true } = options\n const client = useFluxbaseClient()\n\n const [settings, setSettings] = useState<SystemSetting[]>([])\n const [isLoading, setIsLoading] = useState(autoFetch)\n const [error, setError] = useState<Error | null>(null)\n\n const fetchSettings = useCallback(async () => {\n try {\n setIsLoading(true)\n setError(null)\n const response = await client.admin.settings.system.list()\n setSettings(response.settings)\n } catch (err) {\n setError(err as Error)\n } finally {\n setIsLoading(false)\n }\n }, [client])\n\n const getSetting = useCallback(\n (key: string): SystemSetting | undefined => {\n return settings.find((s) => s.key === key)\n },\n [settings]\n )\n\n const updateSetting = useCallback(\n async (key: string, update: UpdateSystemSettingRequest): Promise<void> => {\n await client.admin.settings.system.update(key, update)\n await fetchSettings()\n },\n [client, fetchSettings]\n )\n\n const deleteSetting = useCallback(\n async (key: string): Promise<void> => {\n await client.admin.settings.system.delete(key)\n await fetchSettings()\n },\n [client, fetchSettings]\n )\n\n useEffect(() => {\n if (autoFetch) {\n fetchSettings()\n }\n }, [autoFetch, fetchSettings])\n\n return {\n settings,\n isLoading,\n error,\n refetch: fetchSettings,\n getSetting,\n updateSetting,\n deleteSetting\n }\n}\n\n// ============================================================================\n// useWebhooks Hook\n// ============================================================================\n\nexport interface UseWebhooksOptions {\n autoFetch?: boolean\n refetchInterval?: number\n}\n\nexport interface UseWebhooksReturn {\n webhooks: Webhook[]\n isLoading: boolean\n error: Error | null\n refetch: () => Promise<void>\n createWebhook: (webhook: CreateWebhookRequest) => Promise<Webhook>\n updateWebhook: (id: string, update: UpdateWebhookRequest) => Promise<Webhook>\n deleteWebhook: (id: string) => Promise<void>\n testWebhook: (id: string) => Promise<void>\n}\n\n/**\n * Hook for managing webhooks\n *\n * @example\n * ```tsx\n * function WebhooksManager() {\n * const { webhooks, isLoading, createWebhook, deleteWebhook } = useWebhooks({\n * autoFetch: true\n * })\n *\n * const handleCreate = async () => {\n * await createWebhook({\n * url: 'https://example.com/webhook',\n * events: ['user.created', 'user.updated'],\n * enabled: true\n * })\n * }\n *\n * return <div>...</div>\n * }\n * ```\n */\nexport function useWebhooks(options: UseWebhooksOptions = {}): UseWebhooksReturn {\n const { autoFetch = true, refetchInterval = 0 } = options\n const client = useFluxbaseClient()\n\n const [webhooks, setWebhooks] = useState<Webhook[]>([])\n const [isLoading, setIsLoading] = useState(autoFetch)\n const [error, setError] = useState<Error | null>(null)\n\n const fetchWebhooks = useCallback(async () => {\n try {\n setIsLoading(true)\n setError(null)\n const response = await client.admin.management.webhooks.list()\n setWebhooks(response.webhooks)\n } catch (err) {\n setError(err as Error)\n } finally {\n setIsLoading(false)\n }\n }, [client])\n\n const createWebhook = useCallback(\n async (webhook: CreateWebhookRequest): Promise<Webhook> => {\n const created = await client.admin.management.webhooks.create(webhook)\n await fetchWebhooks()\n return created\n },\n [client, fetchWebhooks]\n )\n\n const updateWebhook = useCallback(\n async (id: string, update: UpdateWebhookRequest): Promise<Webhook> => {\n const updated = await client.admin.management.webhooks.update(id, update)\n await fetchWebhooks()\n return updated\n },\n [client, fetchWebhooks]\n )\n\n const deleteWebhook = useCallback(\n async (id: string): Promise<void> => {\n await client.admin.management.webhooks.delete(id)\n await fetchWebhooks()\n },\n [client, fetchWebhooks]\n )\n\n const testWebhook = useCallback(\n async (id: string): Promise<void> => {\n await client.admin.management.webhooks.test(id)\n },\n [client]\n )\n\n useEffect(() => {\n if (autoFetch) {\n fetchWebhooks()\n }\n\n if (refetchInterval > 0) {\n const interval = setInterval(fetchWebhooks, refetchInterval)\n return () => clearInterval(interval)\n }\n }, [autoFetch, refetchInterval, fetchWebhooks])\n\n return {\n webhooks,\n isLoading,\n error,\n refetch: fetchWebhooks,\n createWebhook,\n updateWebhook,\n deleteWebhook,\n testWebhook\n }\n}\n"],"mappings":"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","names":["useQuery","useMutation","useQueryClient","useQuery","client","useQueryClient","useMutation","useQueryClient","useQueryClient","useMutation","useQuery","useQueryClient","useQuery","useQueryClient","useMutation","useQuery","useMutation","useQuery","useMutation","useEffect","user","useEffect","useState","useEffect","useCallback","useState","useCallback","useEffect","useState","useEffect","useCallback","useState","useCallback","useEffect","useState","useEffect","useCallback","useState","useCallback","useEffect"]}
@@ -0,0 +1,513 @@
1
+ /**
2
+ * Complete Admin Dashboard Example
3
+ *
4
+ * This example demonstrates all admin hooks in a real-world dashboard interface.
5
+ *
6
+ * Features:
7
+ * - Admin authentication with protected routes
8
+ * - User management with pagination
9
+ * - API key management
10
+ * - Webhook configuration
11
+ * - App and system settings
12
+ * - Real-time statistics
13
+ */
14
+
15
+ import React, { useState } from 'react'
16
+ import {
17
+ useAdminAuth,
18
+ useUsers,
19
+ useAPIKeys,
20
+ useWebhooks,
21
+ useAppSettings,
22
+ useSystemSettings
23
+ } from '@fluxbase/sdk-react'
24
+ import type { EnrichedUser } from '@fluxbase/sdk'
25
+
26
+ // ============================================================================
27
+ // Admin Login Component
28
+ // ============================================================================
29
+
30
+ function AdminLogin() {
31
+ const { isAuthenticated, isLoading, error, login } = useAdminAuth({ autoCheck: true })
32
+ const [email, setEmail] = useState('')
33
+ const [password, setPassword] = useState('')
34
+ const [loginError, setLoginError] = useState<string | null>(null)
35
+
36
+ const handleSubmit = async (e: React.FormEvent) => {
37
+ e.preventDefault()
38
+ setLoginError(null)
39
+
40
+ try {
41
+ await login(email, password)
42
+ } catch (err) {
43
+ setLoginError((err as Error).message)
44
+ }
45
+ }
46
+
47
+ if (isLoading) {
48
+ return (
49
+ <div className="flex items-center justify-center min-h-screen">
50
+ <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
51
+ </div>
52
+ )
53
+ }
54
+
55
+ if (isAuthenticated) {
56
+ return null // Will be redirected by parent
57
+ }
58
+
59
+ return (
60
+ <div className="min-h-screen flex items-center justify-center bg-gray-50">
61
+ <div className="max-w-md w-full space-y-8 p-8 bg-white rounded-lg shadow">
62
+ <div>
63
+ <h2 className="text-center text-3xl font-extrabold text-gray-900">
64
+ Admin Login
65
+ </h2>
66
+ </div>
67
+ <form className="mt-8 space-y-6" onSubmit={handleSubmit}>
68
+ <div className="rounded-md shadow-sm -space-y-px">
69
+ <div>
70
+ <input
71
+ type="email"
72
+ required
73
+ className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
74
+ placeholder="Email address"
75
+ value={email}
76
+ onChange={(e) => setEmail(e.target.value)}
77
+ />
78
+ </div>
79
+ <div>
80
+ <input
81
+ type="password"
82
+ required
83
+ className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
84
+ placeholder="Password"
85
+ value={password}
86
+ onChange={(e) => setPassword(e.target.value)}
87
+ />
88
+ </div>
89
+ </div>
90
+
91
+ {(loginError || error) && (
92
+ <div className="text-red-600 text-sm">
93
+ {loginError || error?.message}
94
+ </div>
95
+ )}
96
+
97
+ <div>
98
+ <button
99
+ type="submit"
100
+ className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
101
+ >
102
+ Sign in
103
+ </button>
104
+ </div>
105
+ </form>
106
+ </div>
107
+ </div>
108
+ )
109
+ }
110
+
111
+ // ============================================================================
112
+ // Statistics Overview
113
+ // ============================================================================
114
+
115
+ function Overview() {
116
+ const { users, total: totalUsers, isLoading: loadingUsers } = useUsers({
117
+ autoFetch: true,
118
+ limit: 5
119
+ })
120
+ const { keys, isLoading: loadingKeys } = useAPIKeys({ autoFetch: true })
121
+ const { webhooks, isLoading: loadingWebhooks } = useWebhooks({ autoFetch: true })
122
+ const { settings, isLoading: loadingSettings } = useAppSettings({ autoFetch: true })
123
+
124
+ const isLoading = loadingUsers || loadingKeys || loadingWebhooks || loadingSettings
125
+
126
+ if (isLoading) {
127
+ return <div className="text-center py-8">Loading overview...</div>
128
+ }
129
+
130
+ const enabledWebhooks = webhooks.filter(w => w.enabled).length
131
+ const activeKeys = keys.filter(k => !k.expires_at || new Date(k.expires_at) > new Date()).length
132
+
133
+ return (
134
+ <div className="space-y-6">
135
+ <h2 className="text-2xl font-bold">Dashboard Overview</h2>
136
+
137
+ {/* Statistics Cards */}
138
+ <div className="grid grid-cols-1 md:grid-cols-4 gap-6">
139
+ <div className="bg-white rounded-lg shadow p-6">
140
+ <div className="text-sm font-medium text-gray-500">Total Users</div>
141
+ <div className="mt-2 text-3xl font-semibold text-gray-900">{totalUsers}</div>
142
+ <div className="mt-2 text-xs text-gray-500">Registered accounts</div>
143
+ </div>
144
+
145
+ <div className="bg-white rounded-lg shadow p-6">
146
+ <div className="text-sm font-medium text-gray-500">API Keys</div>
147
+ <div className="mt-2 text-3xl font-semibold text-gray-900">{activeKeys}</div>
148
+ <div className="mt-2 text-xs text-gray-500">{keys.length - activeKeys} expired</div>
149
+ </div>
150
+
151
+ <div className="bg-white rounded-lg shadow p-6">
152
+ <div className="text-sm font-medium text-gray-500">Webhooks</div>
153
+ <div className="mt-2 text-3xl font-semibold text-gray-900">{enabledWebhooks}</div>
154
+ <div className="mt-2 text-xs text-gray-500">{webhooks.length - enabledWebhooks} disabled</div>
155
+ </div>
156
+
157
+ <div className="bg-white rounded-lg shadow p-6">
158
+ <div className="text-sm font-medium text-gray-500">Features</div>
159
+ <div className="mt-2 space-y-1">
160
+ <div className="flex items-center text-sm">
161
+ <span className={`w-2 h-2 rounded-full mr-2 ${settings?.features?.enable_realtime ? 'bg-green-500' : 'bg-gray-300'}`}></span>
162
+ Realtime
163
+ </div>
164
+ <div className="flex items-center text-sm">
165
+ <span className={`w-2 h-2 rounded-full mr-2 ${settings?.features?.enable_storage ? 'bg-green-500' : 'bg-gray-300'}`}></span>
166
+ Storage
167
+ </div>
168
+ <div className="flex items-center text-sm">
169
+ <span className={`w-2 h-2 rounded-full mr-2 ${settings?.features?.enable_functions ? 'bg-green-500' : 'bg-gray-300'}`}></span>
170
+ Functions
171
+ </div>
172
+ </div>
173
+ </div>
174
+ </div>
175
+
176
+ {/* Recent Users */}
177
+ <div className="bg-white rounded-lg shadow">
178
+ <div className="p-6">
179
+ <h3 className="text-lg font-medium mb-4">Recent Users</h3>
180
+ <div className="space-y-3">
181
+ {users.slice(0, 5).map((user) => (
182
+ <div key={user.id} className="flex items-center justify-between">
183
+ <div>
184
+ <div className="font-medium">{user.email}</div>
185
+ <div className="text-sm text-gray-500">
186
+ {new Date(user.created_at).toLocaleDateString()}
187
+ </div>
188
+ </div>
189
+ <div className="flex items-center space-x-2">
190
+ <span className={`px-2 py-1 text-xs rounded ${
191
+ user.role === 'admin' ? 'bg-purple-100 text-purple-800' : 'bg-gray-100 text-gray-800'
192
+ }`}>
193
+ {user.role}
194
+ </span>
195
+ <span className={`px-2 py-1 text-xs rounded ${
196
+ user.email_confirmed ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800'
197
+ }`}>
198
+ {user.email_confirmed ? 'Verified' : 'Pending'}
199
+ </span>
200
+ </div>
201
+ </div>
202
+ ))}
203
+ </div>
204
+ </div>
205
+ </div>
206
+ </div>
207
+ )
208
+ }
209
+
210
+ // ============================================================================
211
+ // User Management
212
+ // ============================================================================
213
+
214
+ function UserManagement() {
215
+ const [page, setPage] = useState(0)
216
+ const [searchEmail, setSearchEmail] = useState('')
217
+ const [roleFilter, setRoleFilter] = useState<'admin' | 'user' | ''>('')
218
+ const limit = 20
219
+
220
+ const {
221
+ users,
222
+ total,
223
+ isLoading,
224
+ error,
225
+ refetch,
226
+ inviteUser,
227
+ updateUserRole,
228
+ deleteUser,
229
+ resetPassword
230
+ } = useUsers({
231
+ autoFetch: true,
232
+ limit,
233
+ offset: page * limit,
234
+ email: searchEmail || undefined,
235
+ role: roleFilter || undefined
236
+ })
237
+
238
+ const handleInvite = async () => {
239
+ const email = prompt('Enter user email:')
240
+ if (!email) return
241
+
242
+ const isAdmin = confirm('Grant admin privileges?')
243
+ try {
244
+ await inviteUser(email, isAdmin ? 'admin' : 'user')
245
+ alert('User invited successfully!')
246
+ } catch (err) {
247
+ alert('Failed to invite user: ' + (err as Error).message)
248
+ }
249
+ }
250
+
251
+ const handleRoleToggle = async (userId: string, currentRole: string) => {
252
+ const newRole = currentRole === 'admin' ? 'user' : 'admin'
253
+ if (confirm(`Change role to ${newRole}?`)) {
254
+ await updateUserRole(userId, newRole)
255
+ }
256
+ }
257
+
258
+ const handleDelete = async (userId: string, email: string) => {
259
+ if (confirm(`Delete user ${email}? This cannot be undone.`)) {
260
+ await deleteUser(userId)
261
+ }
262
+ }
263
+
264
+ const handleResetPassword = async (userId: string) => {
265
+ try {
266
+ const newPassword = await resetPassword(userId)
267
+ alert(`New password: ${newPassword}\n\nMake sure to save this - it won't be shown again!`)
268
+ } catch (err) {
269
+ alert('Failed to reset password: ' + (err as Error).message)
270
+ }
271
+ }
272
+
273
+ if (error) {
274
+ return <div className="text-red-600">Error: {error.message}</div>
275
+ }
276
+
277
+ return (
278
+ <div className="space-y-6">
279
+ <div className="flex justify-between items-center">
280
+ <h2 className="text-2xl font-bold">User Management</h2>
281
+ <button
282
+ onClick={handleInvite}
283
+ className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
284
+ >
285
+ Invite User
286
+ </button>
287
+ </div>
288
+
289
+ {/* Filters */}
290
+ <div className="bg-white rounded-lg shadow p-4">
291
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
292
+ <input
293
+ type="text"
294
+ placeholder="Search by email..."
295
+ value={searchEmail}
296
+ onChange={(e) => {
297
+ setSearchEmail(e.target.value)
298
+ setPage(0)
299
+ }}
300
+ className="px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
301
+ />
302
+ <select
303
+ value={roleFilter}
304
+ onChange={(e) => {
305
+ setRoleFilter(e.target.value as any)
306
+ setPage(0)
307
+ }}
308
+ className="px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
309
+ >
310
+ <option value="">All Roles</option>
311
+ <option value="admin">Admin</option>
312
+ <option value="user">User</option>
313
+ </select>
314
+ <button
315
+ onClick={refetch}
316
+ className="px-4 py-2 border border-gray-300 rounded hover:bg-gray-50"
317
+ >
318
+ Refresh
319
+ </button>
320
+ </div>
321
+ </div>
322
+
323
+ {/* Users Table */}
324
+ <div className="bg-white rounded-lg shadow overflow-hidden">
325
+ {isLoading ? (
326
+ <div className="text-center py-8">Loading users...</div>
327
+ ) : (
328
+ <>
329
+ <table className="min-w-full divide-y divide-gray-200">
330
+ <thead className="bg-gray-50">
331
+ <tr>
332
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
333
+ User
334
+ </th>
335
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
336
+ Role
337
+ </th>
338
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
339
+ Status
340
+ </th>
341
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
342
+ Created
343
+ </th>
344
+ <th className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
345
+ Actions
346
+ </th>
347
+ </tr>
348
+ </thead>
349
+ <tbody className="bg-white divide-y divide-gray-200">
350
+ {users.map((user) => (
351
+ <tr key={user.id}>
352
+ <td className="px-6 py-4 whitespace-nowrap">
353
+ <div className="text-sm font-medium text-gray-900">{user.email}</div>
354
+ <div className="text-xs text-gray-500">{user.id.substring(0, 8)}</div>
355
+ </td>
356
+ <td className="px-6 py-4 whitespace-nowrap">
357
+ <span className={`px-2 py-1 text-xs rounded ${
358
+ user.role === 'admin' ? 'bg-purple-100 text-purple-800' : 'bg-gray-100 text-gray-800'
359
+ }`}>
360
+ {user.role}
361
+ </span>
362
+ </td>
363
+ <td className="px-6 py-4 whitespace-nowrap">
364
+ <span className={`px-2 py-1 text-xs rounded ${
365
+ user.email_confirmed ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800'
366
+ }`}>
367
+ {user.email_confirmed ? 'Verified' : 'Pending'}
368
+ </span>
369
+ </td>
370
+ <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
371
+ {new Date(user.created_at).toLocaleDateString()}
372
+ </td>
373
+ <td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2">
374
+ <button
375
+ onClick={() => handleRoleToggle(user.id, user.role)}
376
+ className="text-blue-600 hover:text-blue-900"
377
+ >
378
+ Toggle Role
379
+ </button>
380
+ <button
381
+ onClick={() => handleResetPassword(user.id)}
382
+ className="text-yellow-600 hover:text-yellow-900"
383
+ >
384
+ Reset PW
385
+ </button>
386
+ <button
387
+ onClick={() => handleDelete(user.id, user.email)}
388
+ className="text-red-600 hover:text-red-900"
389
+ >
390
+ Delete
391
+ </button>
392
+ </td>
393
+ </tr>
394
+ ))}
395
+ </tbody>
396
+ </table>
397
+
398
+ {/* Pagination */}
399
+ <div className="bg-gray-50 px-6 py-3 flex items-center justify-between">
400
+ <div className="text-sm text-gray-700">
401
+ Showing {page * limit + 1} to {Math.min((page + 1) * limit, total)} of {total} users
402
+ </div>
403
+ <div className="space-x-2">
404
+ <button
405
+ onClick={() => setPage(page - 1)}
406
+ disabled={page === 0}
407
+ className="px-3 py-1 border border-gray-300 rounded disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-100"
408
+ >
409
+ Previous
410
+ </button>
411
+ <button
412
+ onClick={() => setPage(page + 1)}
413
+ disabled={(page + 1) * limit >= total}
414
+ className="px-3 py-1 border border-gray-300 rounded disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-100"
415
+ >
416
+ Next
417
+ </button>
418
+ </div>
419
+ </div>
420
+ </>
421
+ )}
422
+ </div>
423
+ </div>
424
+ )
425
+ }
426
+
427
+ // ============================================================================
428
+ // Main Dashboard
429
+ // ============================================================================
430
+
431
+ type TabType = 'overview' | 'users' | 'keys' | 'webhooks' | 'settings'
432
+
433
+ export default function AdminDashboard() {
434
+ const { user, isAuthenticated, isLoading, logout } = useAdminAuth({ autoCheck: true })
435
+ const [activeTab, setActiveTab] = useState<TabType>('overview')
436
+
437
+ if (isLoading) {
438
+ return (
439
+ <div className="flex items-center justify-center min-h-screen">
440
+ <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
441
+ </div>
442
+ )
443
+ }
444
+
445
+ if (!isAuthenticated) {
446
+ return <AdminLogin />
447
+ }
448
+
449
+ const tabs: { id: TabType; label: string }[] = [
450
+ { id: 'overview', label: 'Overview' },
451
+ { id: 'users', label: 'Users' },
452
+ { id: 'keys', label: 'API Keys' },
453
+ { id: 'webhooks', label: 'Webhooks' },
454
+ { id: 'settings', label: 'Settings' }
455
+ ]
456
+
457
+ return (
458
+ <div className="min-h-screen bg-gray-100">
459
+ {/* Header */}
460
+ <header className="bg-white shadow">
461
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
462
+ <div className="flex justify-between items-center">
463
+ <h1 className="text-2xl font-bold text-gray-900">Admin Dashboard</h1>
464
+ <div className="flex items-center space-x-4">
465
+ <div className="text-sm text-gray-700">
466
+ <span className="font-medium">{user?.email}</span>
467
+ <span className="ml-2 px-2 py-1 text-xs rounded bg-purple-100 text-purple-800">
468
+ {user?.role}
469
+ </span>
470
+ </div>
471
+ <button
472
+ onClick={logout}
473
+ className="px-4 py-2 text-sm font-medium text-gray-700 hover:text-gray-900"
474
+ >
475
+ Logout
476
+ </button>
477
+ </div>
478
+ </div>
479
+ </div>
480
+ </header>
481
+
482
+ {/* Navigation Tabs */}
483
+ <nav className="bg-white shadow-sm">
484
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
485
+ <div className="flex space-x-8">
486
+ {tabs.map((tab) => (
487
+ <button
488
+ key={tab.id}
489
+ onClick={() => setActiveTab(tab.id)}
490
+ className={`py-4 px-1 border-b-2 font-medium text-sm ${
491
+ activeTab === tab.id
492
+ ? 'border-blue-500 text-blue-600'
493
+ : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
494
+ }`}
495
+ >
496
+ {tab.label}
497
+ </button>
498
+ ))}
499
+ </div>
500
+ </div>
501
+ </nav>
502
+
503
+ {/* Main Content */}
504
+ <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
505
+ {activeTab === 'overview' && <Overview />}
506
+ {activeTab === 'users' && <UserManagement />}
507
+ {activeTab === 'keys' && <div className="text-center py-8">API Keys management (implement using useAPIKeys)</div>}
508
+ {activeTab === 'webhooks' && <div className="text-center py-8">Webhooks management (implement using useWebhooks)</div>}
509
+ {activeTab === 'settings' && <div className="text-center py-8">Settings management (implement using useAppSettings and useSystemSettings)</div>}
510
+ </main>
511
+ </div>
512
+ )
513
+ }