Skip to content

Commit 0ed9dd2

Browse files
committed
feat: add the Dashboard functionality when user is logged in
1 parent 18c57a4 commit 0ed9dd2

5 files changed

Lines changed: 152 additions & 181 deletions

File tree

README.md

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -306,24 +306,7 @@ at niko.hoffren@gmail.com
306306

307307
## License
308308

309-
This project is licensed under the MIT License.
310-
311-
## Disabled Features
312-
313-
This document provides information about the features that are currently
314-
disabled on this website.
315-
316-
### Dashboard
317-
318-
The Dashboard feature is currently disabled because it needs more development.
319-
It should show all pull requests that the user has merged, but it is not working
320-
properly. When user logs in using Clerk, the dashboard will get stuck in a loop
321-
and will not show the user's pull requests immidiately. After clicking somewhere
322-
else on the website, the dashboard will show the user's pull requests. The pull
323-
request table also needs to be styled properly.
324-
325-
Currently, when "Dashboard" text is replaced with text "Login" in the navigation
326-
bar, the dashboard feature is disabled.
309+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file.
327310

328311
---
329312

components/Dashboard.jsx

Lines changed: 93 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
/* eslint-disable react-hooks/exhaustive-deps */
2-
31
import { useRouter } from 'next/router'
42
import axios from 'axios'
53
import React, { useEffect, useState } from 'react'
64
import { useUser } from '@clerk/nextjs'
75
import { fetchGitHubUsername } from '../utils/fetchClosedPullRequests'
86
import Spinner from '../components/Spinner'
7+
import Image from 'next/image'
98

109
async function fetchStoredPullRequests(username) {
1110
const res = await fetch(`/api/getStoredPullRequests?username=${username}`)
1211
const data = await res.json()
13-
1412
return data
1513
}
1614

@@ -19,25 +17,27 @@ export default function Dashboard() {
1917
const router = useRouter()
2018
const [pullRequests, setPullRequests] = useState([])
2119
const [isLoading, setIsLoading] = useState(false)
20+
const [githubUsername, setGithubUsername] = useState('')
2221

2322
useEffect(() => {
2423
if (!user) {
25-
router.push('/')
24+
router.push('/sign-in')
2625
return
2726
}
2827

2928
const fetchData = async () => {
3029
setIsLoading(true)
3130
try {
32-
const username = await fetchGitHubUsername(user.user.emailAddresses)
31+
const username = await fetchGitHubUsername(user.emailAddresses)
32+
setGithubUsername(username)
3333
const storedPRs = await fetchStoredPullRequests(username)
3434
setPullRequests(storedPRs.pullRequests || [])
3535
const response = await axios.get(
3636
`/api/closedPullRequests?username=${username}`
3737
)
3838
setPullRequests(response.data)
3939
} catch (error) {
40-
// Silent fail
40+
console.error('Error fetching pull requests:', error)
4141
} finally {
4242
setIsLoading(false)
4343
}
@@ -50,142 +50,98 @@ export default function Dashboard() {
5050
}
5151

5252
return (
53-
<>
54-
{/* <div className='pb-4 text-center'>
55-
<h3 className='pb-6 text-2xl'>Successfully Merged Pull Requests:</h3>
56-
</div> */}
57-
<div className='-50 text-center'>
58-
{isLoading && (
59-
<div className='flex h-[200px] items-center justify-center'>
60-
<Spinner />
53+
<div className='mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8'>
54+
{/* User Profile Section */}
55+
<div className='mb-8 rounded-lg bg-slate-800 p-6 shadow-lg'>
56+
<div className='flex items-center space-x-6'>
57+
<div className='relative h-24 w-24'>
58+
<Image
59+
src={user.imageUrl}
60+
alt='Profile'
61+
fill
62+
className='rounded-full border-4 border-slate-600 object-cover'
63+
/>
6164
</div>
62-
)}
65+
<div>
66+
<h2 className='text-2xl font-bold text-white'>{user.fullName}</h2>
67+
<p className='text-slate-400'>GitHub: {githubUsername}</p>
68+
<p className='text-slate-400'>
69+
{user.primaryEmailAddress?.emailAddress}
70+
</p>
71+
<div className='mt-2'>
72+
<span className='inline-flex items-center rounded-full bg-green-100 px-3 py-1 text-sm font-medium text-green-800'>
73+
{pullRequests.length} Contributions
74+
</span>
75+
</div>
76+
</div>
77+
</div>
78+
</div>
79+
80+
{/* Pull Requests Section */}
81+
<div className='rounded-lg bg-slate-800 p-6 shadow-lg'>
82+
<h3 className='mb-6 text-2xl font-bold text-white'>
83+
Successfully Merged Pull Requests
84+
</h3>
6385

64-
{pullRequests.length > 0 && (
65-
<div className='py-4 text-center'>
66-
<table className='min-w-full bg-slate-900'>
67-
<thead>
68-
<tr>
69-
<th className='border-b border-gray-200 bg-gray-900 px-4 py-2 text-left text-xs font-medium uppercase leading-4 tracking-wider'>
70-
Title
71-
</th>
72-
<th className='border-b border-gray-200 bg-gray-900 px-4 py-2 text-left text-xs font-medium uppercase leading-4 tracking-wider'>
73-
Issue
74-
</th>
75-
</tr>
76-
</thead>
77-
<tbody>
78-
{pullRequests.map((pr, index) => (
79-
<tr
80-
key={index}
81-
className={index % 2 === 0 ? 'bg-gray-900' : 'bg-gray-800'}
82-
>
83-
<td className='whitespace-no-wrap px-4 py-4 text-sm leading-5'>
84-
{pr.title}
85-
</td>
86-
<td className='whitespace-no-wrap px-4 py-4 text-sm leading-5'>
87-
{pr.issue}
88-
</td>
86+
{isLoading ? (
87+
<div className='flex h-64 items-center justify-center'>
88+
<Spinner />
89+
</div>
90+
) : pullRequests.length > 0 ? (
91+
<div className='overflow-hidden rounded-lg border border-slate-700'>
92+
<div className='overflow-x-auto'>
93+
<table className='min-w-full divide-y divide-slate-700'>
94+
<thead className='bg-slate-900'>
95+
<tr>
96+
<th
97+
scope='col'
98+
className='px-6 py-4 text-left text-sm font-semibold text-slate-300'
99+
>
100+
Title
101+
</th>
102+
<th
103+
scope='col'
104+
className='px-6 py-4 text-left text-sm font-semibold text-slate-300'
105+
>
106+
Issue
107+
</th>
108+
<th
109+
scope='col'
110+
className='px-6 py-4 text-left text-sm font-semibold text-slate-300'
111+
>
112+
Status
113+
</th>
89114
</tr>
90-
))}
91-
</tbody>
92-
</table>
115+
</thead>
116+
<tbody className='divide-y divide-slate-700 bg-slate-800'>
117+
{pullRequests.map((pr, index) => (
118+
<tr
119+
key={index}
120+
className='transition-colors hover:bg-slate-700'
121+
>
122+
<td className='whitespace-normal px-6 py-4 text-sm text-slate-300'>
123+
{pr.title}
124+
</td>
125+
<td className='whitespace-normal px-6 py-4 text-sm text-slate-300'>
126+
{pr.issue}
127+
</td>
128+
<td className='whitespace-nowrap px-6 py-4 text-sm'>
129+
<span className='inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800'>
130+
Merged
131+
</span>
132+
</td>
133+
</tr>
134+
))}
135+
</tbody>
136+
</table>
137+
</div>
138+
</div>
139+
) : (
140+
<div className='py-12 text-center'>
141+
<p className='text-slate-400'>No pull requests found.</p>
93142
</div>
94143
)}
95144
</div>
96-
</>
145+
</div>
97146
)
98147
}
99-
100-
// import axios from 'axios'
101-
// import React, { useEffect, useState } from 'react'
102-
// import { useUser } from '@clerk/nextjs'
103-
// import { fetchGitHubUsername } from '../utils/fetchClosedPullRequests'
104-
// import Spinner from "../components/Spinner";
105-
106-
// async function fetchStoredPullRequests(username) {
107-
// const res = await fetch(`/api/getStoredPullRequests?username=${username}`)
108-
// const data = await res.json()
109-
110-
// return data
111-
// }
112-
113-
// export default function Dashboard() {
114-
// const user = useUser()
115-
// const [pullRequests, setPullRequests] = useState([])
116-
// const [isLoading, setIsLoading] = useState(false)
117-
118-
// useEffect(() => {
119-
// const fetchData = async () => {
120-
// if (user) {
121-
// setIsLoading(true)
122-
// try {
123-
// const username = await fetchGitHubUsername(user.user.emailAddresses)
124-
// const storedPRs = await fetchStoredPullRequests(username)
125-
// setPullRequests(storedPRs.pullRequests || [])
126-
// const response = await axios.get(
127-
// `/api/closedPullRequests?username=${username}`
128-
// )
129-
// setPullRequests(response.data)
130-
// } catch (error) {
131-
// console.error('Error:', error)
132-
// } finally {
133-
// setIsLoading(false)
134-
// }
135-
// }
136-
// }
137-
// fetchData()
138-
// }, [])
139-
140-
// if (user) {
141-
// return (
142-
// <>
143-
// <div className='pb-4 text-center'>
144-
// <h3 className='pb-6 text-2xl'>Successfully Merged Pull Requests:</h3>
145-
// </div>
146-
// <div className='-50 text-center'>
147-
// {isLoading && (
148-
// <div className='flex h-[200px] items-center justify-center'>
149-
// <Spinner />
150-
// </div>
151-
// )}
152-
153-
// {pullRequests.length > 0 && (
154-
// <div className='py-4 text-center'>
155-
// <table className='min-w-full bg-slate-900'>
156-
// <thead>
157-
// <tr>
158-
// <th className='border-b border-gray-200 bg-gray-900 px-4 py-2 text-left text-xs font-medium uppercase leading-4 tracking-wider '>
159-
// Title
160-
// </th>
161-
// <th className='border-b border-gray-200 bg-gray-900 px-4 py-2 text-left text-xs font-medium uppercase leading-4 tracking-wider '>
162-
// Issue
163-
// </th>
164-
// </tr>
165-
// </thead>
166-
// <tbody>
167-
// {pullRequests.map((pr, index) => (
168-
// <tr
169-
// key={index}
170-
// className={
171-
// index % 2 === 0 ? 'bg-gray-900' : 'bg-gray-800'
172-
// }
173-
// >
174-
// <td className='whitespace-no-wrap px-4 py-4 text-sm leading-5 '>
175-
// {pr.title}
176-
// </td>
177-
// <td className='whitespace-no-wrap px-4 py-4 text-sm leading-5 '>
178-
// {pr.issue}
179-
// </td>
180-
// </tr>
181-
// ))}
182-
// </tbody>
183-
// </table>
184-
// </div>
185-
// )}
186-
// </div>
187-
// </>
188-
// )
189-
// }
190-
// }
191-

components/layout/Header.tsx

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -292,24 +292,35 @@ const Header = () => {
292292

293293
{isOpen && (
294294
<div className='fixed left-0 top-0 z-20 h-full w-64 overflow-auto bg-transparent px-1 pt-6 backdrop-blur-md md:hidden [&>a>div]:font-bold [&>li>div]:font-bold [&>li>div]:transition-all'>
295+
{isLoaded && user && (
296+
<>
297+
<Link
298+
href='/dashboard'
299+
onClick={() => setIsOpen(false)}
300+
className='group relative mb-2 block rounded-md px-6 text-sm font-medium text-black focus:outline-none'
301+
>
302+
<span className='relative block rounded bg-slate-100 px-4 py-2 text-sm font-semibold transition-colors hover:bg-slate-300'>
303+
Dashboard
304+
</span>
305+
</Link>
306+
<div className='ml-6 flex flex-col items-start gap-2 pt-2'>
307+
<UserButton afterSignOutUrl='/' />
308+
<ThemeSelector />
309+
</div>
310+
</>
311+
)}
312+
295313
{!user && (
296314
<Link
297-
href='/dashboard'
315+
href='/sign-in'
298316
onClick={() => setIsOpen(false)}
299317
className='group relative mb-2 block rounded-md px-6 text-sm font-medium text-black focus:outline-none'
300318
>
301319
<span className='relative block rounded bg-slate-100 px-4 py-2 text-sm font-semibold transition-colors hover:bg-slate-300'>
302320
Login
303-
{/* Replace "Login" text with "Dashboard" after Dashboard is implemented and move it out from the !user condition check to show it always */}
304321
</span>
305322
</Link>
306323
)}
307-
{isLoaded && user && (
308-
<div className='ml-6 flex flex-col items-start gap-2 pt-2'>
309-
<UserButton afterSignOutUrl='/' />
310-
<ThemeSelector />
311-
</div>
312-
)}
313324

314325
<Link href='/' onClick={() => setIsOpen(false)}>
315326
<div className='mb-3 mt-4 block cursor-pointer px-6 text-sm transition-transform hover:scale-105'>
@@ -691,25 +702,33 @@ const Header = () => {
691702
</ul>
692703
)}
693704
</li>
705+
{isLoaded && user && (
706+
<>
707+
<Link
708+
href='/dashboard'
709+
className='group relative block rounded-md pl-4 text-sm font-medium text-black focus:outline-none'
710+
>
711+
<span className='relative block rounded bg-slate-100 px-4 py-1 text-sm font-semibold transition-colors hover:bg-slate-300'>
712+
Dashboard
713+
</span>
714+
</Link>
715+
<div className='flex items-center gap-2 pl-4'>
716+
<ThemeSelector />
717+
<UserButton afterSignOutUrl='/' />
718+
</div>
719+
</>
720+
)}
721+
694722
{!user && (
695723
<Link
696-
href='/dashboard'
697-
onClick={() => setIsOpen(false)}
724+
href='/sign-in'
698725
className='group relative block rounded-md pl-4 text-sm font-medium text-black focus:outline-none'
699726
>
700727
<span className='relative block rounded bg-slate-100 px-4 py-1 text-sm font-semibold transition-colors hover:bg-slate-300'>
701728
Login
702-
{/* Replace "Login" text with "Dashboard" after Dashboard is implemented and move it out from the !user condition check to show it always */}
703729
</span>
704730
</Link>
705731
)}
706-
707-
{isLoaded && user && (
708-
<div className='flex items-center gap-2 pl-4'>
709-
<ThemeSelector />
710-
<UserButton afterSignOutUrl='/' />
711-
</div>
712-
)}
713732
</div>
714733
</div>
715734
</div>
@@ -719,3 +738,5 @@ const Header = () => {
719738
}
720739

721740
export default Header
741+
742+

0 commit comments

Comments
 (0)