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+ }
0 commit comments