-
-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add home page and profile page (#36)
* feat: add tailwind plugin to centering dividers divider from tailiwind are based on border, often times it's not centered enough because the padding is not balanced between dividers, here I tried to balance it using this plugin. It also can adjust the amount padding needed. * feat: add home and test profile page + move token retrieval in custom hooks useAuth + add custom hooks to check current tab is github profile url and opensauced user + add activeTab permission to manifest.json to check url of current tab from extension popup + refactor routing logic to use context api, so it can be used anywhere without prop drilling + refactor setRenderedPage to accept props, because I need to pass username to profile page + wrap setRenderedPage with setCurrentPage function to make it easy if we want to update route state without passing props + add new util cacheFetch to be able cache data from api, reducing api calls. + add isOpenSaucedUser function for ease of use and clarity * add real profile page + add getUserData on fetchOpenSaucedApiData to fetch more detailed user data + add getUserPRData to fetch user PR data Note: I use inline styling 'flexWrap' in profile page because it's interfering with github page (again) maybe we should use shadow DOM approach? * refactor: remove comments * fix: add target blank to links in profile page to make it work * chore: Replaced literals with config values * chore: replaced literals with values from cofig * Replaced literals in fetchOpenSaucedApiData.ts with config vals * fix dashboard url and link text as suggested Co-authored-by: Brian Douglas <[email protected]> * clarify tools button text as suggested Co-authored-by: Brian Douglas <[email protected]> Co-authored-by: Anush <[email protected]> * fix profile blog url as suggested Co-authored-by: Brian Douglas <[email protected]> * patching user bio text in profile page as suggested Co-authored-by: Anush <[email protected]> * patch and refactor cachedFetch as suggeted Changes: + Replaced || with ?? to avoid a falsy result when expiry is set to 0 seconds. + Replaced localStorage with chrome.storage.local . + Combined the text/*, application/json check into a single match() call. + Formatted the file with semi-colons(Linting will be added soon, so this shouldn't be a problem). Co-authored-by: Anush <[email protected]> * removing console.log (s), as suggested Co-authored-by: Anush <[email protected]> * removing console.log (s), as suggested Co-authored-by: Anush <[email protected]> * refactor: use Promise.all for concurrent network calls, as suggested Suggestion: Using Promise.all() will prove useful here as the calls can be made simultaneously Co-authored-by: Anush <[email protected]> * fix: user.blog_url to user.blog to match api response --------- Co-authored-by: Anush <[email protected]> Co-authored-by: Brian Douglas <[email protected]>
- Loading branch information
1 parent
1c337c2
commit 8a43288
Showing
10 changed files
with
441 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { useEffect, useState } from "react" | ||
import { OPEN_SAUCED_AUTH_TOKEN_KEY, OPEN_SAUCED_SESSION_ENDPOINT } from "../constants" | ||
import { cachedFetch } from "../utils/cache" | ||
|
||
const removeTokenFromStorage = () => { | ||
return new Promise((resolve, reject) => { | ||
|
||
chrome.storage.sync.remove(OPEN_SAUCED_AUTH_TOKEN_KEY, () => { | ||
resolve(true) | ||
}) | ||
}) | ||
} | ||
|
||
export const useAuth = () => { | ||
const [authToken, setAuthToken] = useState<null | string>(null) | ||
const [user, setUser] = useState<null | { id: string, user_name: string }>(null) | ||
const [isTokenValid, setIsTokenValid] = useState<boolean|null>(null) | ||
|
||
useEffect(() => { | ||
chrome.storage.sync.get([OPEN_SAUCED_AUTH_TOKEN_KEY], (result) => { | ||
if (result[OPEN_SAUCED_AUTH_TOKEN_KEY]) { | ||
setAuthToken(result[OPEN_SAUCED_AUTH_TOKEN_KEY]) | ||
//get account data | ||
cachedFetch(OPEN_SAUCED_SESSION_ENDPOINT, { | ||
expireInSeconds: 2 * 60 * 60, // 2 hours | ||
headers: { | ||
Authorization: `Bearer ${result[OPEN_SAUCED_AUTH_TOKEN_KEY]}`, | ||
Accept: 'application/json', | ||
}, | ||
}).then((resp) => { | ||
if (!resp.ok) { | ||
console.log('error getting user info') | ||
removeTokenFromStorage().then(() => { | ||
setAuthToken(null) | ||
setUser(null) | ||
setIsTokenValid(false) | ||
}) | ||
} | ||
return resp.json() | ||
}) | ||
.then((json) => { | ||
setUser(json) | ||
setIsTokenValid(true) | ||
}) | ||
} else { | ||
setIsTokenValid(false) | ||
} | ||
}); | ||
}, []) | ||
|
||
return { authToken, user, isTokenValid } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { useEffect, useState } from "react" | ||
import { isOpenSaucedUser } from "../utils/fetchOpenSaucedApiData" | ||
import { getGithubUsername } from "../utils/urlMatchers" | ||
|
||
export const useOpensaucedUserCheck = () => { | ||
const [currentTabIsOpensaucedUser, setCurrentTabIsOpensaucedUser] = useState(false) | ||
const [checkedUser, setCheckedUser] = useState<string|null>(null) | ||
useEffect(() => { | ||
//get active tab | ||
chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => { | ||
if (tabs.length > 0) { | ||
const tab = tabs[0] | ||
const username = getGithubUsername(tab.url!) | ||
if(username != null) { | ||
setCheckedUser(username) | ||
setCurrentTabIsOpensaucedUser(await isOpenSaucedUser(username)) | ||
} else { | ||
setCheckedUser(null) | ||
setCurrentTabIsOpensaucedUser(false) | ||
} | ||
} | ||
}) | ||
}, []) | ||
|
||
|
||
return { currentTabIsOpensaucedUser, checkedUser } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import { useContext, useEffect, useState } from 'react'; | ||
import { FaBrain, FaChevronLeft, FaRobot } from 'react-icons/fa'; | ||
import { RiLinkedinFill, RiLinkM, RiTwitterFill } from 'react-icons/ri'; | ||
import { AiOutlineReload } from 'react-icons/ai'; | ||
import { SiC, SiCplusplus, SiCsharp, SiGoland, SiJavascript, SiPhp, SiPython, SiReact, SiRuby, SiRust, SiTypescript } from 'react-icons/si'; | ||
import { DiJava } from 'react-icons/di' | ||
import OpenSaucedLogo from "../assets/opensauced-logo.svg"; | ||
import { getUserData, getUserPRData } from '../utils/fetchOpenSaucedApiData'; | ||
import { RouteContext } from '../App'; | ||
|
||
const interestIcon = { | ||
'python': <SiPython />, | ||
'java': <DiJava />, | ||
'javascript': <SiJavascript />, | ||
'typescript': <SiTypescript />, | ||
'csharp': <SiCsharp />, | ||
'cpp': <SiCplusplus />, | ||
'c': <SiC />, | ||
'php': <SiPhp />, | ||
'ruby': <SiRuby />, | ||
'react': <SiReact />, | ||
'ml': <FaBrain />, | ||
'ai': <FaRobot />, | ||
'golang': <SiGoland />, | ||
'rust': <SiRust /> | ||
} | ||
|
||
type InterestIconKeys = keyof typeof interestIcon; | ||
|
||
export const Profile = () => { | ||
const { page, setCurrentPage } = useContext(RouteContext) | ||
const [user, setUser] = useState<null | { id: string, user_name: string, bio: string, created_at: string, linkedin_url: string, twitter_username: string, blog: string, interests: string, open_issues: number }>(null) | ||
const [userPR, setUserPR] = useState<null | { meta: {itemCount: number} }>(null) | ||
|
||
useEffect(() => { | ||
const fetchUserData = async () => { | ||
const [userData, userPRData] = await Promise.all([getUserData(page.props.userName), getUserPRData(page.props.userName)]); | ||
setUser(userData); | ||
setUserPR(userPRData); | ||
} | ||
fetchUserData(); | ||
}, []) | ||
|
||
|
||
return ( | ||
<div className="grid grid-cols-1 divide-y divider-y-center-2 min-w-[320px] text-white"> | ||
<header className='flex justify-between'> | ||
<div className="flex items-center gap-2"> | ||
<button onClick={() => { setCurrentPage("home") }} className='rounded-full p-2 bg-slate-700 hover:bg-slate-700/50'> | ||
<FaChevronLeft className='text-osOrange' /> | ||
</button> | ||
<img src={OpenSaucedLogo} alt="OpenSauced logo" className='w-[100%]' /> | ||
</div> | ||
<button | ||
title='Refresh user data' | ||
className='hover:text-orange text-lg' | ||
onClick={async () => { | ||
const [userData, userPRData] = await Promise.all([getUserData(page.props.userName), getUserPRData(page.props.userName)]); | ||
setUser(userData); | ||
setUserPR(userPRData); | ||
}}> | ||
<AiOutlineReload /> | ||
</button> | ||
</header> | ||
<main> | ||
<div className='flex flex-col items-center gap-1 mb-4'> | ||
<img | ||
src={`https://github.com/${page.props.userName}.png`} | ||
alt="profile image" | ||
className='rounded-full w-14 aspect-square p-1 bg-slate-700' /> | ||
<p className='font-medium'>@{page.props.userName}</p> | ||
{(user?.linkedin_url || user?.twitter_username) && | ||
<div className='social flex gap-0.5'> | ||
{user?.linkedin_url && | ||
<a | ||
target={'_blank'} | ||
href={user.linkedin_url} | ||
title={user.linkedin_url} | ||
className='rounded-sm border bg-slate-700 hover:bg-slate-700/50 hover:text-orange p-1'> | ||
<RiLinkedinFill className='text-lg' /> | ||
</a> | ||
} | ||
{user?.twitter_username && | ||
<a | ||
target={'_blank'} | ||
href={`https://twitter.com/${user.twitter_username}`} | ||
title={`https://twitter.com/${user.twitter_username}`} | ||
className='rounded-sm border bg-slate-700 hover:bg-slate-700/50 hover:text-orange p-1'> | ||
<RiTwitterFill className='text-lg' /> | ||
</a> | ||
} | ||
</div> | ||
} | ||
{user?.bio && <span>{user.bio}</span>} | ||
{user?.blog && | ||
<a target={'_blank'} href={user.blog} className='flex text-orange items-center gap-0.5'> | ||
<RiLinkM /> | ||
{user.blog} | ||
</a> | ||
} | ||
</div> | ||
<div className='grid grid-cols-2 text-white bg-osOrange -mx-4 mb-4 p-4 py-8'> | ||
<div className='flex flex-col items-center justify-center p-2 text-xs'> | ||
<p>Open Issues</p> | ||
<p className='font-medium text-5xl'>{user?.open_issues}</p> | ||
</div> | ||
<div className='flex flex-col items-center justify-center p-2 text-xs'> | ||
<p>PRs Made</p> | ||
<p className='font-medium text-5xl'>{userPR?.meta.itemCount}</p> | ||
</div> | ||
<div className='flex flex-col items-center justify-center p-2 text-xs'> | ||
<p>Avg PRs Velocity</p> | ||
<p className='font-medium text-5xl'>-</p> | ||
</div> | ||
<div className='flex flex-col items-center justify-center p-2 text-xs'> | ||
<p>Contributed Repos</p> | ||
<p className='font-medium text-5xl'>-</p> | ||
</div> | ||
</div> | ||
{ | ||
<div> | ||
<h2 className='font-medium text-lg mb-2'>Current Interest</h2> | ||
<div className='flex gap-1.5' style={{flexWrap: 'wrap'}}> | ||
{user?.interests.split(',').map((interest) => ( | ||
<a target="_blank" key={interest} | ||
href={`https://insights.opensauced.pizza/${interest}/dashboard/filter/recent`} | ||
title={`https://insights.opensauced.pizza/${interest}/dashboard/filter/recent`} | ||
className='flex gap-1 items-center p-1.5 px-4 rounded-full bg-slate-700 hover:bg-slate-700/50'> | ||
{interestIcon[interest as InterestIconKeys] ?? null} | ||
{interest} | ||
</a> | ||
) | ||
)} | ||
</div> | ||
</div> | ||
} | ||
</main> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.