{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "realtime-avatar-stack-nuxtjs", "type": "registry:block", "title": "Realtime Avatar Stack", "description": "Component which stack of avatars, tracked by realtime presence.", "dependencies": [ "@supabase/supabase-js@latest" ], "registryDependencies": [ "avatar", "tooltip" ], "files": [ { "path": "registry/default/realtime-avatar-stack/nuxtjs/app/components/realtime-avatar-stack.vue", "content": "\n\n\n", "type": "registry:component", "target": "app/components/realtime-avatar-stack.vue" }, { "path": "registry/default/realtime-avatar-stack/nuxtjs/app/components/avatar-stack.vue", "content": "\n\n\n", "type": "registry:component", "target": "app/components/avatar-stack.vue" }, { "path": "registry/default/realtime-avatar-stack/nuxtjs/app/composables/useCurrentUserImage.ts", "content": "import { ref, onMounted } from 'vue'\n// @ts-ignore\nimport { createClient } from '@/lib/supabase/client'\n\nexport function useCurrentUserImage() {\n const image = ref(null)\n\n const fetchUserImage = async () => {\n const { data, error } = await createClient().auth.getSession()\n\n if (error) {\n console.error(error)\n return\n }\n\n image.value = data.session?.user.user_metadata.avatar_url ?? null\n }\n\n onMounted(() => {\n fetchUserImage()\n })\n\n return {\n image,\n }\n}\n", "type": "registry:component", "target": "app/composables/useCurrentUserImage.ts" }, { "path": "registry/default/realtime-avatar-stack/nuxtjs/app/composables/useCurrentUserName.ts", "content": "import { ref, onMounted } from 'vue'\n// @ts-ignore\nimport { createClient } from '@/lib/supabase/client'\n\nexport function useCurrentUserName() {\n const name = ref(null)\n\n const fetchProfileName = async () => {\n const { data, error } = await createClient().auth.getSession()\n\n if (error) {\n console.error(error)\n return\n }\n\n name.value = data.session?.user.user_metadata.full_name ?? '?'\n }\n\n onMounted(() => {\n fetchProfileName()\n })\n\n return {\n name,\n }\n}\n", "type": "registry:component", "target": "app/composables/useCurrentUserName.ts" }, { "path": "registry/default/realtime-avatar-stack/nuxtjs/app/composables/useRealtimePresenceRoom.ts", "content": "import { ref, watch, onUnmounted } from 'vue'\nimport { REALTIME_SUBSCRIBE_STATES } from '@supabase/supabase-js'\n\nimport { useCurrentUserImage } from './useCurrentUserImage'\nimport { useCurrentUserName } from './useCurrentUserName'\n// @ts-ignore\nimport { createClient } from '@/lib/supabase/client'\n\nconst supabase = createClient()\n\nexport type RealtimeUser = {\n id: string\n name: string\n image: string\n}\n\nexport function useRealtimePresenceRoom(roomName: string) {\n const { image: currentUserImage } = useCurrentUserImage()\n const { name: currentUserName } = useCurrentUserName()\n\n const users = ref>({})\n\n let room: ReturnType | null = null\n\n function setupRoom() {\n if (!roomName) return\n\n 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 const typedValues = values as Array<{ image: string; name: string }>\n return [\n key,\n {\n id: key,\n name: typedValues[0].name,\n image: typedValues[0].image,\n },\n ]\n })\n ) as Record\n\n users.value = newUsers\n })\n .subscribe(async (status: REALTIME_SUBSCRIBE_STATES) => {\n if (status === REALTIME_SUBSCRIBE_STATES.SUBSCRIBED && room) {\n await room.track({\n name: currentUserName.value,\n image: currentUserImage.value,\n })\n } else {\n users.value = {}\n }\n })\n }\n\n function cleanup() {\n if (room) {\n room.unsubscribe()\n room = null\n }\n }\n\n watch(\n [() => roomName, currentUserName, currentUserImage],\n () => {\n cleanup()\n setupRoom()\n },\n { immediate: true }\n )\n\n onUnmounted(() => {\n cleanup()\n })\n\n return { users }\n}\n", "type": "registry:component", "target": "app/composables/useRealtimePresenceRoom.ts" } ] }