Subscription finally working!
This commit is contained in:
64
src/components/status/TechTable.tsx
Normal file → Executable file
64
src/components/status/TechTable.tsx
Normal file → Executable file
@ -9,7 +9,7 @@ import {
|
|||||||
updateUserStatus,
|
updateUserStatus,
|
||||||
type UserWithStatus,
|
type UserWithStatus,
|
||||||
} from '@/lib/hooks/status';
|
} from '@/lib/hooks/status';
|
||||||
import { Drawer, DrawerTrigger, Progress } from '@/components/ui';
|
import { Drawer, DrawerTrigger, Loading } from '@/components/ui';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { HistoryDrawer } from '@/components/status';
|
import { HistoryDrawer } from '@/components/status';
|
||||||
import type { Profile } from '@/utils/supabase';
|
import type { Profile } from '@/utils/supabase';
|
||||||
@ -24,7 +24,7 @@ export const TechTable = ({
|
|||||||
initialStatuses = [],
|
initialStatuses = [],
|
||||||
className = 'w-full max-w-7xl mx-auto px-4',
|
className = 'w-full max-w-7xl mx-auto px-4',
|
||||||
}: TechTableProps) => {
|
}: TechTableProps) => {
|
||||||
const { isAuthenticated, profile } = useAuth();
|
const { isAuthenticated } = useAuth();
|
||||||
const { tvMode } = useTVMode();
|
const { tvMode } = useTVMode();
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
||||||
@ -34,6 +34,7 @@ export const TechTable = ({
|
|||||||
useState<UserWithStatus[]>(initialStatuses);
|
useState<UserWithStatus[]>(initialStatuses);
|
||||||
const [selectedHistoryUser, setSelectedHistoryUser] =
|
const [selectedHistoryUser, setSelectedHistoryUser] =
|
||||||
useState<Profile | null>(null);
|
useState<Profile | null>(null);
|
||||||
|
const channelRef = useRef<RealtimeChannel | null>(null);
|
||||||
|
|
||||||
const fetchRecentUsersWithStatuses = useCallback(async () => {
|
const fetchRecentUsersWithStatuses = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@ -112,6 +113,63 @@ export const TechTable = ({
|
|||||||
);
|
);
|
||||||
}, [selectedIds.length, usersWithStatuses.length]);
|
}, [selectedIds.length, usersWithStatuses.length]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isAuthenticated) return;
|
||||||
|
const supabase = createClient();
|
||||||
|
|
||||||
|
const channel = supabase
|
||||||
|
.channel('status_updates', {
|
||||||
|
config: { broadcast: { self: true }}
|
||||||
|
})
|
||||||
|
.on('broadcast', { event: 'status_updated' }, (payload) => {
|
||||||
|
const { user_status } = payload.payload as {
|
||||||
|
user_status: UserWithStatus;
|
||||||
|
timestamp: string;
|
||||||
|
};
|
||||||
|
console.log('Received status update:', user_status);
|
||||||
|
|
||||||
|
setUsersWithStatuses((prevUsers) => {
|
||||||
|
const existingUserIndex = prevUsers.findIndex((u) =>
|
||||||
|
u.user.id === user_status.user.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingUserIndex !== -1) {
|
||||||
|
const updatedUsers = [...prevUsers];
|
||||||
|
updatedUsers[existingUserIndex] = {
|
||||||
|
user: user_status.user, // Use the user from the broadcast
|
||||||
|
status: user_status.status,
|
||||||
|
created_at: user_status.created_at,
|
||||||
|
updated_by: user_status.updated_by,
|
||||||
|
};
|
||||||
|
return updatedUsers;
|
||||||
|
} else {
|
||||||
|
// Add new user to list!
|
||||||
|
return [user_status, ...prevUsers];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.subscribe((status) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
|
||||||
|
if (status === 'SUBSCRIBED') {
|
||||||
|
console.log('Successfully subscribed to status updates!');
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
|
||||||
|
} else if (status === 'CHANNEL_ERROR') {
|
||||||
|
console.error('Error subscribing to status updates.')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
channelRef.current = channel;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (channelRef.current) {
|
||||||
|
supabase.removeChannel(channelRef.current).catch((error) => {
|
||||||
|
console.error(`Error unsubscribing from status updates: ${error}`);
|
||||||
|
});
|
||||||
|
channelRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [isAuthenticated]);
|
||||||
|
|
||||||
const formatTime = (timestamp: string) => {
|
const formatTime = (timestamp: string) => {
|
||||||
const date = new Date(timestamp);
|
const date = new Date(timestamp);
|
||||||
const time = date.toLocaleTimeString('en-US', {
|
const time = date.toLocaleTimeString('en-US', {
|
||||||
@ -126,7 +184,7 @@ export const TechTable = ({
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className='flex justify-center items-center min-h-[400px]'>
|
<div className='flex justify-center items-center min-h-[400px]'>
|
||||||
<Progress value={33} className='w-64' />
|
<Loading className='w-full' alpha={0.5} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
0
src/components/status/_TechTable.tsx
Normal file
0
src/components/status/_TechTable.tsx
Normal file
@ -8,6 +8,7 @@ export * from './dropdown-menu';
|
|||||||
export * from './form';
|
export * from './form';
|
||||||
export * from './input';
|
export * from './input';
|
||||||
export * from './label';
|
export * from './label';
|
||||||
|
export * from './loading';
|
||||||
export * from './pagination';
|
export * from './pagination';
|
||||||
export * from './progress';
|
export * from './progress';
|
||||||
export * from './scroll-area';
|
export * from './scroll-area';
|
||||||
|
41
src/components/ui/loading.tsx
Normal file
41
src/components/ui/loading.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
'use client';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Progress } from '@/components/ui/progress';
|
||||||
|
import type * as ProgressPrimitive from '@radix-ui/react-progress';
|
||||||
|
|
||||||
|
type Loading_Props = React.ComponentProps<typeof ProgressPrimitive.Root> & {
|
||||||
|
/** how many ms between updates */
|
||||||
|
intervalMs?: number;
|
||||||
|
/** fraction of the remaining distance to add each tick */
|
||||||
|
alpha?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Loading: React.FC<Loading_Props> = ({
|
||||||
|
intervalMs = 50,
|
||||||
|
alpha = 0.1,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: Loading_Props) => {
|
||||||
|
const [progress, setProgress] = React.useState(0);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const id = window.setInterval(() => {
|
||||||
|
setProgress((prev) => {
|
||||||
|
// compute the next progress
|
||||||
|
const next = prev + (100 - prev) * alpha;
|
||||||
|
// optional: round if you want neat numbers
|
||||||
|
return Math.min(100, Math.round(next * 10) / 10);
|
||||||
|
});
|
||||||
|
}, intervalMs);
|
||||||
|
|
||||||
|
return () => window.clearInterval(id);
|
||||||
|
}, [intervalMs, alpha]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="items-center justify-center w-1/3 m-auto pt-20">
|
||||||
|
<Progress value={progress} className={className} {...props} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Loading;
|
@ -22,7 +22,7 @@ function Progress({
|
|||||||
<ProgressPrimitive.Indicator
|
<ProgressPrimitive.Indicator
|
||||||
data-slot='progress-indicator'
|
data-slot='progress-indicator'
|
||||||
className='bg-primary h-full w-full flex-1 transition-all'
|
className='bg-primary h-full w-full flex-1 transition-all'
|
||||||
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
style={{ transform: `translateX(-${100 - (value ?? 0)}%)` }}
|
||||||
/>
|
/>
|
||||||
</ProgressPrimitive.Root>
|
</ProgressPrimitive.Root>
|
||||||
);
|
);
|
||||||
|
@ -30,14 +30,12 @@ export const getRecentUsersWithStatuses = async (): Promise<
|
|||||||
|
|
||||||
const { data, error } = (await supabase
|
const { data, error } = (await supabase
|
||||||
.from('statuses')
|
.from('statuses')
|
||||||
.select(
|
.select(`
|
||||||
`
|
|
||||||
user:profiles!user_id(*),
|
user:profiles!user_id(*),
|
||||||
status,
|
status,
|
||||||
created_at,
|
created_at,
|
||||||
updated_by:profiles!updated_by_id(*)
|
updated_by:profiles!updated_by_id(*)
|
||||||
`,
|
`)
|
||||||
)
|
|
||||||
.gte('created_at', oneDayAgo.toISOString())
|
.gte('created_at', oneDayAgo.toISOString())
|
||||||
.order('created_at', { ascending: false })) as {
|
.order('created_at', { ascending: false })) as {
|
||||||
data: UserWithStatus[];
|
data: UserWithStatus[];
|
||||||
@ -172,6 +170,7 @@ export const updateUserStatus = async (
|
|||||||
user: userProfile,
|
user: userProfile,
|
||||||
status: insertedStatus.status,
|
status: insertedStatus.status,
|
||||||
created_at: insertedStatus.created_at,
|
created_at: insertedStatus.created_at,
|
||||||
|
updated_by: userProfile,
|
||||||
};
|
};
|
||||||
|
|
||||||
await broadcastStatusUpdates([userStatus]);
|
await broadcastStatusUpdates([userStatus]);
|
||||||
|
Reference in New Issue
Block a user