Files
shorturl-analytics/app/components/ui/TeamSelector.tsx
2025-04-01 19:04:13 +08:00

164 lines
4.2 KiB
TypeScript

"use client";
import * as React from 'react';
import { useEffect, useState } from 'react';
import type { Database } from '@/types/supabase';
import { getSupabaseClient } from '../../utils/supabase';
import { AuthChangeEvent, Session } from '@supabase/supabase-js';
import { Select } from '@radix-ui/themes';
import { Loader2 } from 'lucide-react';
import { cn } from '@/lib/utils';
type Team = Database['limq']['Tables']['teams']['Row'];
type AccentColor =
| 'ruby' | 'gray' | 'gold' | 'bronze' | 'brown' | 'yellow' | 'amber' | 'orange'
| 'tomato' | 'red' | 'crimson' | 'pink' | 'plum' | 'purple' | 'violet'
| 'iris' | 'indigo' | 'blue' | 'cyan' | 'teal' | 'jade' | 'green' | 'grass' | 'lime';
// TeamSelector component using Radix Themes
export function TeamSelector({
value,
onChange,
className,
size = "2",
variant = "surface",
color,
radius,
}: {
value?: string;
onChange?: (teamId: string) => void;
className?: string;
size?: "1" | "2" | "3";
variant?: "classic" | "surface" | "soft" | "ghost";
color?: AccentColor;
radius?: "none" | "small" | "medium" | "large" | "full";
}) {
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [teams, setTeams] = useState<Team[]>([]);
useEffect(() => {
let isMounted = true;
const fetchTeams = async (userId: string) => {
if (!isMounted) return;
setLoading(true);
setError(null);
try {
const supabase = getSupabaseClient();
const { data: memberships, error: membershipError } = await supabase
.from('team_membership')
.select('team_id')
.eq('user_id', userId);
if (membershipError) throw membershipError;
if (!memberships || memberships.length === 0) {
if (isMounted) setTeams([]);
return;
}
const teamIds = memberships.map(m => m.team_id);
const { data: teamsData, error: teamsError } = await supabase
.from('teams')
.select('*')
.in('id', teamIds)
.is('deleted_at', null);
if (teamsError) throw teamsError;
if (isMounted && teamsData) {
setTeams(teamsData);
}
} catch (err) {
if (isMounted) {
setError(err instanceof Error ? err.message : 'Failed to load teams');
}
} finally {
if (isMounted) {
setLoading(false);
}
}
};
const supabase = getSupabaseClient();
const { data: { subscription } } = supabase.auth.onAuthStateChange((event: AuthChangeEvent, session: Session | null) => {
if (event === 'SIGNED_IN' && session?.user?.id) {
fetchTeams(session.user.id);
} else if (event === 'SIGNED_OUT') {
setTeams([]);
setError(null);
}
});
supabase.auth.getSession().then(({ data: { session } }) => {
if (session?.user?.id) {
fetchTeams(session.user.id);
}
});
return () => {
isMounted = false;
subscription.unsubscribe();
};
}, []);
if (loading) {
return (
<div className={cn(
"flex w-full items-center justify-between rounded-md border px-3 py-2",
className
)}>
<Loader2 className="h-4 w-4 animate-spin" />
</div>
);
}
if (error) {
return (
<div className={cn(
"flex w-full items-center rounded-md border border-destructive bg-destructive/10 px-3 py-2 text-destructive",
className
)}>
{error}
</div>
);
}
if (teams.length === 0) {
return (
<div className={cn(
"flex w-full items-center rounded-md border px-3 py-2 text-muted-foreground",
className
)}>
No teams available
</div>
);
}
return (
<Select.Root
value={value}
onValueChange={onChange}
size={size}
>
<Select.Trigger
className={className}
variant={variant}
color={color}
radius={radius}
placeholder="Select a team"
/>
<Select.Content position="popper">
{teams.map((team) => (
<Select.Item key={team.id} value={team.id}>
{team.name}
</Select.Item>
))}
</Select.Content>
</Select.Root>
);
}