Skip to content

Commit 44e37e1

Browse files
authored
Merge pull request #52 from MuslimeKaya/main
feat: implement Quick Wins tab with GitHub issue filters and tabbed layout
2 parents 6ffc31e + 24858ed commit 44e37e1

File tree

14 files changed

+1410
-122
lines changed

14 files changed

+1410
-122
lines changed

package-lock.json

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"@radix-ui/react-separator": "^1.1.7",
1717
"@radix-ui/react-slot": "^1.2.3",
1818
"@radix-ui/react-tabs": "^1.1.12",
19+
"@tanstack/react-table": "^8.21.3",
1920
"chart.js": "^4.5.0",
2021
"class-variance-authority": "^0.7.1",
2122
"clsx": "^2.1.1",

src/app/quick-wins/page.tsx

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
// src/app/quick-wins/page.tsx
2+
'use client'
3+
4+
import { useState, useEffect } from 'react'
5+
import { useSearchParams, useRouter } from 'next/navigation'
6+
import { Layout } from '@/components/layout/Layout'
7+
8+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
9+
import { Button } from '@/components/ui/button'
10+
import { Badge } from '@/components/ui/badge'
11+
import { Alert, AlertDescription } from '@/components/ui/alert'
12+
13+
import { useRequireAuth } from '@/hooks/useAuth'
14+
import { useQuickWins } from '@/components/quick-wins/hooks/useQuickWins'
15+
import { QuickWinsTable } from '@/components/quick-wins/QuickWinsTable'
16+
import { SearchModal } from '@/components/search/SearchModal'
17+
import { ThemeToggle } from '@/components/theme/ThemeToggle'
18+
19+
import {
20+
Lightbulb,
21+
Wrench,
22+
Search,
23+
RefreshCw,
24+
AlertTriangle,
25+
Info,
26+
Github
27+
} from 'lucide-react'
28+
import { useSearchStore } from '@/stores'
29+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
30+
31+
export default function QuickWinsPage() {
32+
const { isLoading, orgData } = useRequireAuth()
33+
const { setSearchModalOpen } = useSearchStore()
34+
35+
const searchParams = useSearchParams()
36+
const router = useRouter()
37+
38+
const VALID_TABS = ['good-issues', 'easy-fixes'] as const
39+
type ValidTab = typeof VALID_TABS[number]
40+
41+
42+
const tabParam = searchParams.get('tab')
43+
const currentTab: ValidTab = VALID_TABS.includes(tabParam as ValidTab)
44+
? (tabParam as ValidTab)
45+
: 'good-issues'
46+
const {
47+
goodIssues,
48+
easyFixes,
49+
loadingGoodIssues,
50+
loadingEasyFixes,
51+
goodIssuesError,
52+
easyFixesError,
53+
refreshGoodIssues,
54+
refreshEasyFixes,
55+
refreshAll,
56+
totalIssues,
57+
needsToken,
58+
hasData
59+
} = useQuickWins()
60+
61+
const handleTabChange = (tab: string) => {
62+
63+
try {
64+
router.push(`/quick-wins?tab=${tab}`)
65+
} catch (error) {
66+
console.error('Failed to navigate to tab:', tab, error)
67+
}
68+
}
69+
const getWelcomeMessage = () => {
70+
const hour = new Date().getHours()
71+
const timeOfDay = hour < 12 ? 'Good morning' : hour < 18 ? 'Good afternoon' : 'Good evening'
72+
const userName = orgData?.orgName || 'Developer'
73+
return `${timeOfDay}, ${userName}! `
74+
}
75+
76+
// Show loading state during initial load
77+
if (isLoading) {
78+
return (
79+
<Layout>
80+
<div className="min-h-screen flex items-center justify-center">
81+
<div className="text-center">
82+
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-indigo-600 mx-auto mb-4"></div>
83+
<p className="text-gray-600">Loading quick wins...</p>
84+
</div>
85+
</div>
86+
</Layout>
87+
)
88+
}
89+
90+
return (
91+
<Layout>
92+
<div className="max-w-7xl mx-auto p-6 space-y-6">
93+
{/* Header */}
94+
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4 border-b pb-4">
95+
<div>
96+
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">
97+
{getWelcomeMessage()}
98+
</h1>
99+
<p className="text-gray-600 dark:text-gray-300 mt-1">
100+
Find easy issues to contribute to • Last updated: {new Date().toLocaleTimeString()}
101+
</p>
102+
</div>
103+
104+
<div className="flex items-center gap-3">
105+
<Button
106+
variant="outline"
107+
onClick={() => setSearchModalOpen(true)}
108+
className="px-6 py-2.5 font-medium text-base"
109+
size="lg"
110+
>
111+
<Search className="w-6 h-6 mr-2" />
112+
Search
113+
</Button>
114+
115+
<Button
116+
variant="outline"
117+
onClick={refreshAll}
118+
disabled={loadingGoodIssues || loadingEasyFixes}
119+
className="px-6 py-2.5 font-medium text-base"
120+
size="lg"
121+
>
122+
<RefreshCw className={`w-6 h-6 mr-2 ${(loadingGoodIssues || loadingEasyFixes) ? 'animate-spin' : ''}`} />
123+
Refresh All
124+
</Button>
125+
126+
<ThemeToggle />
127+
</div>
128+
</div>
129+
130+
{/* Hero Section */}
131+
<div className="mb-8">
132+
<div className="flex items-center gap-2 mb-2">
133+
<Lightbulb className="w-6 h-6 text-yellow-500" />
134+
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">
135+
Quick Wins
136+
</h1>
137+
</div>
138+
<p className="text-gray-600 dark:text-gray-300">
139+
Discover easy issues and good first contributions to jumpstart your open source journey
140+
</p>
141+
</div>
142+
143+
{/* No Token Warning */}
144+
{needsToken && (
145+
<Alert className="border-yellow-200 bg-yellow-50" role="alert" aria-live="polite">
146+
<AlertTriangle className="h-4 w-4 text-yellow-600" />
147+
<AlertDescription className="text-yellow-800">
148+
<strong>GitHub Token Required:</strong> To access more issues and avoid rate limits,
149+
please add your GitHub token in the{' '}
150+
<Button variant="link" className="h-auto p-0 text-yellow-700 underline" asChild>
151+
<a href="/login" aria-label="Go to login page to add GitHub token">login page</a>
152+
</Button>
153+
. Without a token, results may be limited.
154+
</AlertDescription>
155+
</Alert>
156+
)}
157+
158+
{/* Stats Cards */}
159+
{hasData && (
160+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
161+
<Card>
162+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
163+
<CardTitle className="text-sm font-medium">Total Issues</CardTitle>
164+
<Github className="h-4 w-4 text-muted-foreground" />
165+
</CardHeader>
166+
<CardContent>
167+
<div className="text-2xl font-bold">{totalIssues}</div>
168+
<p className="text-xs text-muted-foreground">
169+
{goodIssues.length} good issues, {easyFixes.length} easy fixes
170+
</p>
171+
</CardContent>
172+
</Card>
173+
174+
<Card>
175+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
176+
<CardTitle className="text-sm font-medium">Good First Issues</CardTitle>
177+
<Lightbulb className="h-4 w-4 text-muted-foreground" />
178+
</CardHeader>
179+
<CardContent>
180+
<div className="text-2xl font-bold">{goodIssues.length}</div>
181+
<p className="text-xs text-muted-foreground">
182+
Perfect for beginners
183+
</p>
184+
</CardContent>
185+
</Card>
186+
187+
<Card>
188+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
189+
<CardTitle className="text-sm font-medium">Easy Fixes</CardTitle>
190+
<Wrench className="h-4 w-4 text-muted-foreground" />
191+
</CardHeader>
192+
<CardContent>
193+
<div className="text-2xl font-bold">{easyFixes.length}</div>
194+
<p className="text-xs text-muted-foreground">
195+
Quick contributions
196+
</p>
197+
</CardContent>
198+
</Card>
199+
</div>
200+
)}
201+
202+
203+
204+
{/* Quick Wins Tabs */}
205+
<Tabs
206+
value={currentTab}
207+
onValueChange={(value) => {
208+
if (VALID_TABS.includes(value as ValidTab)) {
209+
handleTabChange(value)
210+
}
211+
}}
212+
className="w-full"
213+
>
214+
<TabsList className="grid w-full grid-cols-2">
215+
<TabsTrigger value="good-issues" className="flex items-center gap-2">
216+
<Lightbulb className="w-4 h-4" />
217+
Good First Issues
218+
<Badge variant="secondary" className="ml-1">{goodIssues.length}</Badge>
219+
</TabsTrigger>
220+
<TabsTrigger value="easy-fixes" className="flex items-center gap-2">
221+
<Wrench className="w-4 h-4" />
222+
Easy Fixes
223+
<Badge variant="secondary" className="ml-1">{easyFixes.length}</Badge>
224+
</TabsTrigger>
225+
</TabsList>
226+
227+
<TabsContent value="good-issues" className="mt-6">
228+
<QuickWinsTable
229+
data={goodIssues}
230+
loading={loadingGoodIssues}
231+
error={goodIssuesError}
232+
onRefresh={refreshGoodIssues}
233+
title="Good First Issues"
234+
description="Well-documented issues perfect for newcomers to open source"
235+
emptyMessage="No good first issues found"
236+
/>
237+
</TabsContent>
238+
239+
<TabsContent value="easy-fixes" className="mt-6">
240+
<QuickWinsTable
241+
data={easyFixes}
242+
loading={loadingEasyFixes}
243+
error={easyFixesError}
244+
onRefresh={refreshEasyFixes}
245+
title="Easy Fixes"
246+
description="Simple bugs and improvements that can be fixed quickly"
247+
emptyMessage="No easy fixes found"
248+
/>
249+
</TabsContent>
250+
</Tabs>
251+
252+
253+
</div>
254+
255+
<SearchModal />
256+
</Layout >
257+
)
258+
}

src/app/search/page.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,6 @@ export default function SearchPage() {
195195
}
196196
}
197197
}, [userParam, repoParam, setCurrentQuery, setCurrentSearchType]);
198-
199198
const performSearch = async (query: string, type: "users" | "repos") => {
200199
setSearchResults((prev) => ({ ...prev, loading: true, error: null }));
201200

@@ -611,12 +610,12 @@ export default function SearchPage() {
611610
{/* Most Active Day calculation */}
612611
{userAnalytics.behavior?.length > 0
613612
? userAnalytics.behavior.reduce(
614-
(max: any, day: any) =>
615-
day.commits + day.prs + day.issues >
613+
(max: any, day: any) =>
614+
day.commits + day.prs + day.issues >
616615
max.commits + max.prs + max.issues
617-
? day
618-
: max
619-
).day
616+
? day
617+
: max
618+
).day
620619
: "N/A"}
621620
</td>
622621
</tr>

src/components/layout/Layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ export function Layout({ children }: { children: React.ReactNode }) {
88
const { isOpen, setOpen } = useSidebarState()
99

1010
return (
11-
<div className="flex h-screen overflow-hidden">
11+
<div className="flex overflow-hidden">
1212
<Sidebar />
1313
<SidebarToggle onClick={() => setOpen(true)} />
14-
14+
1515
<main className="flex-1 overflow-y-auto lg:ml-0">
1616
{children}
1717
</main>

0 commit comments

Comments
 (0)