- Backend: user_activity service (user_activity_log + project_activity)
- Backend: stars service (repo_star + project_follow)
- Backend: user_get_following_list (with is_following_me mutual check)
- Frontend: Tab navigation on /user/{username} with Overview/Activity/Following/Stars/Security
- Frontend: UserActivity timeline, FollowingList grid, StarsList, SecurityTab (SSH keys + PATs)
79 lines
3.1 KiB
TypeScript
79 lines
3.1 KiB
TypeScript
import { useQuery } from '@tanstack/react-query';
|
|
import { Activity, Clock } from 'lucide-react';
|
|
import { Card, CardContent } from '@/components/ui/card';
|
|
import { getUserActivity, type UserActivityItem } from '@/client';
|
|
import { formatDate } from './utils';
|
|
|
|
export function UserActivity({ username }: { username: string }) {
|
|
const { data, isLoading, isError } = useQuery({
|
|
queryKey: ['user-activity', username],
|
|
queryFn: async () => {
|
|
const resp = await getUserActivity({ path: { username } });
|
|
return resp.data?.data ?? null;
|
|
},
|
|
retry: false,
|
|
});
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<Card className="border-border/40">
|
|
<CardContent className="py-8 text-center text-sm text-muted-foreground">
|
|
Loading activity...
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
if (isError || !data) {
|
|
return (
|
|
<Card className="border-border/40">
|
|
<CardContent className="py-8 text-center text-sm text-muted-foreground">
|
|
Unable to load activity.
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
if (data.items.length === 0) {
|
|
return (
|
|
<Card className="border-border/40">
|
|
<CardContent className="py-12 text-center">
|
|
<Activity className="mx-auto h-8 w-8 text-muted-foreground/40" />
|
|
<p className="mt-2 text-sm text-muted-foreground">No activity yet</p>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
const getTypeColor = (type: string) => {
|
|
if (type === 'auth') return 'bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400';
|
|
return 'bg-green-100 dark:bg-green-900/30 text-green-600 dark:text-green-400';
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-3">
|
|
{data.items.map((item: UserActivityItem) => (
|
|
<Card key={`${item.activity_type}-${item.id}`} className="border-border/40">
|
|
<CardContent className="flex items-start gap-3 py-4">
|
|
<span className={`mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-xs font-medium ${getTypeColor(item.activity_type)}`}>
|
|
{item.activity_type === 'auth' ? 'A' : 'P'}
|
|
</span>
|
|
<div className="min-w-0 flex-1">
|
|
<p className="text-sm font-medium">{item.title}</p>
|
|
{item.resource_name && (
|
|
<p className="text-xs text-muted-foreground mt-0.5">
|
|
Project: {item.resource_name}
|
|
</p>
|
|
)}
|
|
<div className="flex items-center gap-1 mt-1 text-xs text-muted-foreground/70">
|
|
<Clock className="h-3 w-3" />
|
|
{formatDate(item.created_at)}
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|