Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -473,3 +473,66 @@ Accessibility: Enhanced focus indicators for keyboard navigation */
.animate-shimmer {
animation: shimmer 2s infinite;
}

/* Tab content animations */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

.animate-fadeIn {
animation: fadeIn 0.3s ease-out;
}
Comment on lines +476 to +491
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove duplicate fadeIn animation definition.

The fadeIn keyframes are already defined at lines 176-186. This duplicate definition is unnecessary and can cause confusion. The utility class names also differ (.animate-fade-in vs .animate-fadeIn), which may lead to inconsistent usage.

Apply this diff to remove the duplicate:

-/* Tab content animations */
-@keyframes fadeIn {
-  from {
-    opacity: 0;
-    transform: translateY(10px);
-  }
-  to {
-    opacity: 1;
-    transform: translateY(0);
-  }
-}
-
 .animate-fadeIn {
   animation: fadeIn 0.3s ease-out;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/* Tab content animations */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fadeIn {
animation: fadeIn 0.3s ease-out;
}
.animate-fadeIn {
animation: fadeIn 0.3s ease-out;
}
🤖 Prompt for AI Agents
In app/globals.css around lines 476-491, remove the duplicate @keyframes fadeIn
block and the .animate-fadeIn utility; instead ensure the single canonical
@keyframes fadeIn (defined at lines 176-186) is used and either rename or map
the utility to the existing class naming convention (.animate-fade-in) so usage
is consistent across the codebase (delete the duplicate keyframes + utility and,
if needed, add/rename a utility to .animate-fade-in that references the existing
animation).


/* Count animation */
@keyframes countUp {
from {
transform: scale(1.2);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}

.animate-countUp {
animation: countUp 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Button press animation */
@keyframes buttonPress {
0% {
transform: scale(1);
}
50% {
transform: scale(0.95);
}
100% {
transform: scale(1);
}
}

.animate-buttonPress {
animation: buttonPress 0.2s ease-in-out;
}

/* Focus visible styles for keyboard navigation */
.focus-visible:focus {
outline: 2px solid hsl(var(--ring));
outline-offset: 2px;
}

/* Touch-friendly button sizes */
@media (max-width: 640px) {
button, a {
min-height: 44px;
min-width: 44px;
}
}
39 changes: 27 additions & 12 deletions app/protected/connections/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,48 +12,63 @@ import { ConnectionStats } from '@/components/connections/ConnectionStats'
export default function ConnectionsPage() {
const [searchQuery, setSearchQuery] = useState('')
const [activeTab, setActiveTab] = useState('following')
const [announcement, setAnnouncement] = useState('')

const handleTabChange = (tab: string) => {
setActiveTab(tab)
const tabNames: Record<string, string> = {
following: 'Following',
followers: 'Followers',
search: 'Search'
}
setAnnouncement(`Switched to ${tabNames[tab]} tab`)
}

return (
<div className="flex flex-col h-[calc(100vh-4rem)] bg-black">
{/* Screen reader announcements */}
<div role="status" aria-live="polite" aria-atomic="true" className="sr-only">
{announcement}
</div>
{/* Header */}
<div className="border-b p-4">
<div className="max-w-7xl mx-auto">
<div className="flex items-center gap-3 mb-4">
<Users className="h-6 w-6 text-primary" />
<h1 className="text-xl md:text-2xl font-bold">Connections</h1>
</div>
<ConnectionStats onTabChange={setActiveTab} />
<ConnectionStats onTabChange={handleTabChange} />
</div>
</div>

{/* Main Content */}
<div className="flex-1 overflow-hidden">
<div className="max-w-7xl mx-auto h-full flex flex-col p-4">
<Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1 flex flex-col">
<TabsList className="grid w-full grid-cols-3 mb-4">
<TabsTrigger value="following" className="gap-2">
<Tabs value={activeTab} onValueChange={handleTabChange} className="flex-1 flex flex-col">
<TabsList className="grid w-full grid-cols-3 mb-4 h-auto">
<TabsTrigger value="following" className="gap-1 sm:gap-2 flex-col sm:flex-row py-2 sm:py-1.5">
<UserPlus className="h-4 w-4" />
<span>Following</span>
<span className="text-xs sm:text-sm">Following</span>
</TabsTrigger>
<TabsTrigger value="followers" className="gap-2">
<TabsTrigger value="followers" className="gap-1 sm:gap-2 flex-col sm:flex-row py-2 sm:py-1.5">
<Users className="h-4 w-4" />
<span>Followers</span>
<span className="text-xs sm:text-sm">Followers</span>
</TabsTrigger>
<TabsTrigger value="search" className="gap-2">
<TabsTrigger value="search" className="gap-1 sm:gap-2 flex-col sm:flex-row py-2 sm:py-1.5">
<Search className="h-4 w-4" />
<span>Search</span>
<span className="text-xs sm:text-sm">Search</span>
</TabsTrigger>
</TabsList>

<TabsContent value="following" className="flex-1 overflow-y-auto space-y-3">
<TabsContent value="following" className="flex-1 overflow-y-auto space-y-3 animate-fadeIn">
<FollowingList />
</TabsContent>

<TabsContent value="followers" className="flex-1 overflow-y-auto space-y-3">
<TabsContent value="followers" className="flex-1 overflow-y-auto space-y-3 animate-fadeIn">
<FollowersList />
</TabsContent>

<TabsContent value="search" className="flex-1 overflow-y-auto space-y-3">
<TabsContent value="search" className="flex-1 overflow-y-auto space-y-3 animate-fadeIn">
<div className="relative mb-4">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" />
<Input
Expand Down
30 changes: 24 additions & 6 deletions components/connections/ConnectionStats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,24 +55,42 @@ export function ConnectionStats({ onTabChange }: ConnectionStatsProps) {
return (
<div className="grid grid-cols-2 gap-4">
<Card
className="p-4 cursor-pointer transition-all duration-300 hover:scale-[1.02] hover:shadow-lg hover:border-primary/50 group"
className="p-4 cursor-pointer transition-all duration-300 hover:scale-[1.02] hover:shadow-lg hover:border-primary/50 group focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2"
onClick={() => onTabChange?.('following')}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
onTabChange?.('following')
}
}}
aria-label={`View following list. You are following ${stats.following} users`}
>
<div className="flex items-center gap-2 text-muted-foreground mb-1 group-hover:text-primary transition-colors">
<UserPlus className="h-4 w-4" />
<UserPlus className="h-4 w-4" aria-hidden="true" />
<span className="text-sm font-medium">Following</span>
</div>
<p className="text-2xl font-bold group-hover:text-primary transition-colors">{stats.following}</p>
<p className="text-2xl font-bold group-hover:text-primary transition-colors animate-countUp">{stats.following}</p>
</Card>
<Card
className="p-4 cursor-pointer transition-all duration-300 hover:scale-[1.02] hover:shadow-lg hover:border-primary/50 group"
className="p-4 cursor-pointer transition-all duration-300 hover:scale-[1.02] hover:shadow-lg hover:border-primary/50 group focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2"
onClick={() => onTabChange?.('followers')}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
onTabChange?.('followers')
}
}}
aria-label={`View followers list. You have ${stats.followers} followers`}
>
<div className="flex items-center gap-2 text-muted-foreground mb-1 group-hover:text-primary transition-colors">
<Users className="h-4 w-4" />
<Users className="h-4 w-4" aria-hidden="true" />
<span className="text-sm font-medium">Followers</span>
</div>
<p className="text-2xl font-bold group-hover:text-primary transition-colors">{stats.followers}</p>
<p className="text-2xl font-bold group-hover:text-primary transition-colors animate-countUp">{stats.followers}</p>
</Card>
</div>
)
Expand Down
21 changes: 12 additions & 9 deletions components/connections/UserCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,10 @@ export function UserCard({ user, connectionStatus, onConnectionChange, showMessa
onClick={handleViewProfile}
size="sm"
variant="ghost"
className="gap-1"
title="View Profile"
className="gap-1 active:animate-buttonPress"
aria-label={`View ${name}'s profile`}
>
<Eye className="h-3 w-3" />
<Eye className="h-3 w-3" aria-hidden="true" />
<span className="hidden md:inline">View</span>
</Button>

Expand All @@ -168,9 +168,10 @@ export function UserCard({ user, connectionStatus, onConnectionChange, showMessa
disabled={loading}
size="sm"
variant="outline"
className="gap-1"
className="gap-1 active:animate-buttonPress"
aria-label={`Send message to ${name}`}
>
<MessageCircle className="h-3 w-3" />
<MessageCircle className="h-3 w-3" aria-hidden="true" />
<span className="hidden sm:inline">Message</span>
</Button>
)}
Expand All @@ -181,19 +182,21 @@ export function UserCard({ user, connectionStatus, onConnectionChange, showMessa
disabled={loading}
size="sm"
variant="outline"
className="gap-1"
className="gap-1 active:animate-buttonPress"
aria-label={`Unfollow ${name}`}
>
<UserMinus className="h-3 w-3" />
<UserMinus className="h-3 w-3" aria-hidden="true" />
<span className="hidden sm:inline">Unfollow</span>
</Button>
) : (
<Button
onClick={handleFollow}
disabled={loading}
size="sm"
className="gap-1"
className="gap-1 active:animate-buttonPress"
aria-label={`Follow ${name}`}
>
<UserPlus className="h-3 w-3" />
<UserPlus className="h-3 w-3" aria-hidden="true" />
<span className="hidden sm:inline">Follow</span>
</Button>
)}
Expand Down
Loading