{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "realtime-avatar-stack-react-router", "type": "registry:component", "title": "Avatar Stack with Realtime Presence", "description": "Component which stack of avatars, tracked by realtime presence.", "dependencies": [ "@supabase/ssr@latest", "@supabase/supabase-js@latest" ], "registryDependencies": [ "avatar", "tooltip" ], "files": [ { "path": "registry/default/blocks/realtime-avatar-stack/components/avatar-stack.tsx", "content": "import { cva, type VariantProps } from 'class-variance-authority'\nimport * as React from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { Avatar, AvatarFallback, AvatarImage } from '@/registry/default/components/ui/avatar'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '@/registry/default/components/ui/tooltip'\n\nconst avatarStackVariants = cva('flex -space-x-4 -space-y-4', {\n variants: {\n orientation: {\n vertical: 'flex-row',\n horizontal: 'flex-col',\n },\n },\n defaultVariants: {\n orientation: 'vertical',\n },\n})\n\nexport interface AvatarStackProps\n extends React.HTMLAttributes, VariantProps {\n avatars: { name: string; image: string }[]\n maxAvatarsAmount?: number\n}\n\nconst AvatarStack = ({\n className,\n orientation,\n avatars,\n maxAvatarsAmount = 3,\n ...props\n}: AvatarStackProps) => {\n const shownAvatars = avatars.slice(0, maxAvatarsAmount)\n const hiddenAvatars = avatars.slice(maxAvatarsAmount)\n\n return (\n \n {shownAvatars.map(({ name, image }, index) => (\n \n \n \n \n \n {name\n ?.split(' ')\n ?.map((word) => word[0])\n ?.join('')\n ?.toUpperCase()}\n \n \n \n \n

{name}

\n
\n
\n ))}\n\n {hiddenAvatars.length ? (\n \n \n \n +{avatars.length - shownAvatars.length}\n \n \n \n {hiddenAvatars.map(({ name }, index) => (\n

{name}

\n ))}\n
\n
\n ) : null}\n \n )\n}\n\nexport { AvatarStack, avatarStackVariants }\n", "type": "registry:component" }, { "path": "registry/default/blocks/realtime-avatar-stack/components/realtime-avatar-stack.tsx", "content": "'use client'\n\nimport { useMemo } from 'react'\n\nimport { AvatarStack } from '@/registry/default/blocks/realtime-avatar-stack/components/avatar-stack'\nimport { useRealtimePresenceRoom } from '@/registry/default/blocks/realtime-avatar-stack/hooks/use-realtime-presence-room'\n\nexport const RealtimeAvatarStack = ({ roomName }: { roomName: string }) => {\n const { users: usersMap } = useRealtimePresenceRoom(roomName)\n const avatars = useMemo(() => {\n return Object.values(usersMap).map((user) => ({\n name: user.name,\n image: user.image,\n }))\n }, [usersMap])\n\n return \n}\n", "type": "registry:component" }, { "path": "registry/default/blocks/realtime-avatar-stack/hooks/use-realtime-presence-room.ts", "content": "'use client'\n\nimport { REALTIME_SUBSCRIBE_STATES } from '@supabase/supabase-js'\nimport { useEffect, useState } from 'react'\n\nimport { useCurrentUserImage } from '@/registry/default/blocks/current-user-avatar/hooks/use-current-user-image'\nimport { useCurrentUserName } from '@/registry/default/blocks/current-user-avatar/hooks/use-current-user-name'\nimport { createClient } from '@/registry/default/clients/react-router/lib/supabase/client'\n\nconst supabase = createClient()\n\nexport type RealtimeUser = {\n id: string\n name: string\n image: string\n}\n\nexport const useRealtimePresenceRoom = (roomName: string) => {\n const currentUserImage = useCurrentUserImage()\n const currentUserName = useCurrentUserName()\n\n const [users, setUsers] = useState>({})\n\n useEffect(() => {\n const room = supabase.channel(roomName)\n\n room\n .on('presence', { event: 'sync' }, () => {\n const newState = room.presenceState<{ image: string; name: string }>()\n\n const newUsers = Object.fromEntries(\n Object.entries(newState).map(([key, values]) => [\n key,\n { name: values[0].name, image: values[0].image },\n ])\n ) as Record\n setUsers(newUsers)\n })\n .subscribe(async (status) => {\n if (status === REALTIME_SUBSCRIBE_STATES.SUBSCRIBED) {\n await room.track({\n name: currentUserName,\n image: currentUserImage,\n })\n } else {\n setUsers({})\n }\n })\n\n return () => {\n room.unsubscribe()\n }\n }, [roomName, currentUserName, currentUserImage])\n\n return { users }\n}\n", "type": "registry:hook" }, { "path": "registry/default/blocks/current-user-avatar/hooks/use-current-user-name.ts", "content": "import { useEffect, useState } from 'react'\n\nimport { createClient } from '@/registry/default/clients/react-router/lib/supabase/client'\n\nexport const useCurrentUserName = () => {\n const [name, setName] = useState(null)\n\n useEffect(() => {\n const fetchProfileName = async () => {\n const { data, error } = await createClient().auth.getSession()\n if (error) {\n console.error(error)\n }\n\n setName(data.session?.user.user_metadata.full_name ?? '?')\n }\n\n fetchProfileName()\n }, [])\n\n return name || '?'\n}\n", "type": "registry:hook" }, { "path": "registry/default/blocks/current-user-avatar/hooks/use-current-user-image.ts", "content": "import { useEffect, useState } from 'react'\n\nimport { createClient } from '@/registry/default/clients/react-router/lib/supabase/client'\n\nexport const useCurrentUserImage = () => {\n const [image, setImage] = useState(null)\n\n useEffect(() => {\n const fetchUserImage = async () => {\n const { data, error } = await createClient().auth.getSession()\n if (error) {\n console.error(error)\n }\n\n setImage(data.session?.user.user_metadata.avatar_url ?? null)\n }\n fetchUserImage()\n }, [])\n\n return image\n}\n", "type": "registry:hook" }, { "path": "registry/default/clients/react-router/lib/supabase/client.ts", "content": "/// \nimport { createBrowserClient } from '@supabase/ssr'\n\nexport function createClient() {\n return createBrowserClient(\n import.meta.env.VITE_SUPABASE_URL!,\n import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY!\n )\n}\n", "type": "registry:lib" }, { "path": "registry/default/clients/react-router/lib/supabase/server.ts", "content": "import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'\n\nexport function createClient(request: Request) {\n const headers = new Headers()\n\n const supabase = createServerClient(\n process.env.VITE_SUPABASE_URL!,\n process.env.VITE_SUPABASE_PUBLISHABLE_KEY!,\n {\n cookies: {\n getAll() {\n return parseCookieHeader(request.headers.get('Cookie') ?? '') as {\n name: string\n value: string\n }[]\n },\n setAll(cookiesToSet) {\n cookiesToSet.forEach(({ name, value, options }) =>\n headers.append('Set-Cookie', serializeCookieHeader(name, value, options))\n )\n },\n },\n }\n )\n\n return { supabase, headers }\n}\n", "type": "registry:lib" } ], "envVars": { "VITE_SUPABASE_URL": "", "VITE_SUPABASE_PUBLISHABLE_KEY": "" }, "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_KEY`." }