From 182f48e20ec5c926d6c8cee5b5f3bda12641be93 Mon Sep 17 00:00:00 2001 From: martin-mfg <2026226+martin-mfg@users.noreply.github.com> Date: Thu, 18 Sep 2025 13:37:10 +0200 Subject: [PATCH] apply all github-trends adaptions so far taken from https://github.com/martin-mfg/github-trends/tree/grs-adaptions --- .../backend/src/aggregation/layer2/auth.py | 1 + .../src/data/mongo/secret/functions.py | 1 + frontend/backend/src/data/mongo/user/get.py | 1 + .../src/data/mongo/user_months/functions.py | 1 + .../backend/src/data/mongo/user_months/get.py | 1 + frontend/backend/src/processing/auth.py | 2 +- frontend/frontend/.env-template | 4 +- frontend/frontend/src/api/user.js | 14 ++-- frontend/frontend/src/components/Card/Card.js | 36 +++++---- frontend/frontend/src/components/Card/SVG.js | 9 +-- .../src/components/Home/CheckboxSection.js | 3 +- .../src/components/Home/NumericSection.js | 79 +++++++++++++++++++ .../src/components/Home/TextSection.js | 63 +++++++++++++++ frontend/frontend/src/pages/App/AppTrends.js | 23 +++--- frontend/frontend/src/pages/App/Footer.js | 20 +++-- frontend/frontend/src/pages/Demo/Demo.js | 8 +- frontend/frontend/src/pages/Home/Home.js | 72 +++++++++-------- .../src/pages/Home/stages/Customize.js | 58 ++++++++++++-- .../frontend/src/pages/Home/stages/Display.js | 16 ++-- .../src/pages/Home/stages/SelectCard.js | 55 ++++++++++--- .../frontend/src/pages/Home/stages/Theme.js | 2 +- .../frontend/src/pages/Landing/Landing.js | 2 +- .../frontend/src/pages/Settings/Settings.js | 16 ++-- frontend/frontend/src/utils.js | 8 ++ 24 files changed, 382 insertions(+), 113 deletions(-) create mode 100644 frontend/frontend/src/components/Home/NumericSection.js create mode 100644 frontend/frontend/src/components/Home/TextSection.js diff --git a/frontend/backend/src/aggregation/layer2/auth.py b/frontend/backend/src/aggregation/layer2/auth.py index 25bae8d9..3311b6c1 100644 --- a/frontend/backend/src/aggregation/layer2/auth.py +++ b/frontend/backend/src/aggregation/layer2/auth.py @@ -23,6 +23,7 @@ async def check_db_user_exists(user_id: str) -> bool: async def check_user_starred_repo( user_id: str, owner: str = OWNER, repo: str = REPO ) -> bool: + return True # Checks the repo's starred users (with cache) try: repo_stargazers = await get_repo_stargazers(owner, repo) diff --git a/frontend/backend/src/data/mongo/secret/functions.py b/frontend/backend/src/data/mongo/secret/functions.py index 809ba919..c3f0bb1b 100644 --- a/frontend/backend/src/data/mongo/secret/functions.py +++ b/frontend/backend/src/data/mongo/secret/functions.py @@ -10,6 +10,7 @@ from src.utils import alru_cache @alru_cache(ttl=timedelta(minutes=15)) async def get_keys(no_cache: bool = False) -> Tuple[bool, List[str]]: + return (False, []) secrets: Optional[Dict[str, Any]] = await SECRETS.find_one({"project": "main"}) if secrets is None: return (False, []) diff --git a/frontend/backend/src/data/mongo/user/get.py b/frontend/backend/src/data/mongo/user/get.py index bf51ab94..6b8ef812 100644 --- a/frontend/backend/src/data/mongo/user/get.py +++ b/frontend/backend/src/data/mongo/user/get.py @@ -11,6 +11,7 @@ from src.utils import alru_cache async def get_public_user( user_id: str, no_cache: bool = False ) -> Tuple[bool, Optional[PublicUserModel]]: + return (False, None) user: Optional[Dict[str, Any]] = await USERS.find_one({"user_id": user_id}) if user is None: # flag is false, don't cache diff --git a/frontend/backend/src/data/mongo/user_months/functions.py b/frontend/backend/src/data/mongo/user_months/functions.py index 78f97667..4a38def9 100644 --- a/frontend/backend/src/data/mongo/user_months/functions.py +++ b/frontend/backend/src/data/mongo/user_months/functions.py @@ -3,6 +3,7 @@ from src.data.mongo.user_months.models import UserMonth async def set_user_month(user_month: UserMonth): + return compressed_user_month = user_month.model_dump() compressed_user_month["data"] = user_month.data.compress() diff --git a/frontend/backend/src/data/mongo/user_months/get.py b/frontend/backend/src/data/mongo/user_months/get.py index 22f09767..8869ea48 100644 --- a/frontend/backend/src/data/mongo/user_months/get.py +++ b/frontend/backend/src/data/mongo/user_months/get.py @@ -10,6 +10,7 @@ from src.models import UserPackage async def get_user_months( user_id: str, private_access: bool, start_month: date, end_month: date ) -> List[UserMonth]: + return [] start = datetime(start_month.year, start_month.month, 1) end = datetime(end_month.year, end_month.month, 28) today = datetime.now() diff --git a/frontend/backend/src/processing/auth.py b/frontend/backend/src/processing/auth.py index c014772a..e2e88143 100644 --- a/frontend/backend/src/processing/auth.py +++ b/frontend/backend/src/processing/auth.py @@ -59,7 +59,7 @@ async def authenticate( end_date=None, ) - await db_update_user(user_id, raw_user) + # await db_update_user(user_id, raw_user) return user_id, background_task diff --git a/frontend/frontend/.env-template b/frontend/frontend/.env-template index 4a52c1c5..a13be7ae 100644 --- a/frontend/frontend/.env-template +++ b/frontend/frontend/.env-template @@ -1,3 +1,3 @@ -REACT_APP_PROD=false +PROD=false -REACT_APP_CLIENT_ID=abc123 \ No newline at end of file +REACT_APP_DEV_CLIENT_ID=abc123 \ No newline at end of file diff --git a/frontend/frontend/src/api/user.js b/frontend/frontend/src/api/user.js index c8514740..578cb5ca 100644 --- a/frontend/frontend/src/api/user.js +++ b/frontend/frontend/src/api/user.js @@ -17,22 +17,22 @@ const setUserKey = async (code) => { } }; -const authenticate = async (code, privateAccess) => { +const authenticate = async (code, privateAccess, userKey) => { try { - const fullUrl = `${URL_PREFIX}/auth/web/login/${code}?private_access=${privateAccess}`; + const fullUrl = `https://github-readme-stats-phi-jet-58.vercel.app/api/authenticate?code=${code}&private_access=${privateAccess}&user_key=${userKey}`; const result = await axios.post(fullUrl); - return result.data.data; + return result.data; } catch (error) { console.error(error); return ''; } }; -const getUserMetadata = async (userId) => { +const getUserMetadata = async (userKey) => { try { - const fullUrl = `${URL_PREFIX}/user/db/get/metadata/${userId}`; + const fullUrl = `https://github-readme-stats-phi-jet-58.vercel.app/api/private-access?user_key=${userKey}`; const result = await axios.get(fullUrl); - return result.data.data; + return result.data; } catch (error) { console.error(error); return ''; @@ -41,7 +41,7 @@ const getUserMetadata = async (userId) => { const deleteAccount = async (userId, userKey) => { try { - const fullUrl = `${URL_PREFIX}/auth/web/delete/${userId}?user_key=${userKey}`; + const fullUrl = `https://github-readme-stats-phi-jet-58.vercel.app/api/delete-user?user_key=${userKey}`; const result = await axios.get(fullUrl); return result.data; // no decorator } catch (error) { diff --git a/frontend/frontend/src/components/Card/Card.js b/frontend/frontend/src/components/Card/Card.js index 7d635018..8ecaae66 100644 --- a/frontend/frontend/src/components/Card/Card.js +++ b/frontend/frontend/src/components/Card/Card.js @@ -1,23 +1,15 @@ import React from 'react'; -import { useSelector } from 'react-redux'; import PropTypes from 'prop-types'; -import { BACKEND_URL } from '../../constants'; - import SVG from './SVG'; import { classnames } from '../../utils'; -export const Image = ({ imageSrc, compact }) => { - const userId = useSelector((state) => state.user.userId); - const fullImageSrc = `${BACKEND_URL}/user/svg/${userId}/${imageSrc}`; +export const Image = ({ imageSrc, compact, extraClasses = '' }) => { + const fullImageSrc = `https://github-readme-stats-phi-jet-58.vercel.app/api/${imageSrc}&client=wizard`; return ( -
- +
+
); }; @@ -25,17 +17,27 @@ export const Image = ({ imageSrc, compact }) => { Image.propTypes = { imageSrc: PropTypes.string.isRequired, compact: PropTypes.bool, + extraClasses: PropTypes.string, }; Image.defaultProps = { compact: false, + extraClasses: '', }; -export const Card = ({ title, description, imageSrc, selected, compact }) => { +export const Card = ({ + title, + description, + imageSrc, + selected, + compact, + fixedSize, +}) => { return (
{ >

{title}

{description}

- +
); }; @@ -54,9 +60,11 @@ Card.propTypes = { imageSrc: PropTypes.string.isRequired, selected: PropTypes.bool, compact: PropTypes.bool, + fixedSize: PropTypes.string, }; Card.defaultProps = { selected: false, compact: false, + fixedSize: false, }; diff --git a/frontend/frontend/src/components/Card/SVG.js b/frontend/frontend/src/components/Card/SVG.js index 6ac60aea..6e680d0a 100644 --- a/frontend/frontend/src/components/Card/SVG.js +++ b/frontend/frontend/src/components/Card/SVG.js @@ -13,10 +13,7 @@ const SvgInline = (props) => { // eslint-disable-next-line no-unused-vars const [loaded, setLoaded] = useState(false); - let url = `${props.url.split('?')[0]}?cache=${Date.now()}`; - if (props.url.split('?').length > 1) { - url += `&${props.url.split('?')[1]}`; - } + const { url } = props; useEffect(() => { setLoaded(false); @@ -39,7 +36,7 @@ const SvgInline = (props) => {
${svg}`, + __html: `
${svg}
`, }} /> ); @@ -49,7 +46,7 @@ const SvgInline = (props) => {
${svg}`, + __html: `
${svg}
`, }} /> ); diff --git a/frontend/frontend/src/components/Home/CheckboxSection.js b/frontend/frontend/src/components/Home/CheckboxSection.js index f5ac1a21..90b765f6 100644 --- a/frontend/frontend/src/components/Home/CheckboxSection.js +++ b/frontend/frontend/src/components/Home/CheckboxSection.js @@ -1,5 +1,6 @@ /* eslint-disable jsx-a11y/interactive-supports-focus */ /* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable react/no-danger */ import React from 'react'; import PropTypes from 'prop-types'; @@ -17,7 +18,7 @@ const CheckboxSection = ({ }) => { return (
-

{text}

+

{ + const [internalValue, setInternalValue] = useState(value); + const debounceTimeout = useRef(null); + + useEffect(() => { + // Debounce setValue + if (debounceTimeout.current) { + clearTimeout(debounceTimeout.current); + } + if (internalValue === value) { + return undefined; + } + debounceTimeout.current = setTimeout(() => { + setValue(internalValue); + }, 1000); + return () => clearTimeout(debounceTimeout.current); + }, [internalValue]); + + useEffect(() => { + setInternalValue(value); + }, [value]); + + return ( +

+

+ setInternalValue(e.target.value)} + min={min} + max={max} + step={step} + disabled={disabled} + placeholder={placeholder} + /> +

+ ); +}; + +NumericSection.propTypes = { + title: PropTypes.string.isRequired, + text: PropTypes.string.isRequired, + value: PropTypes.number, + setValue: PropTypes.func.isRequired, + min: PropTypes.number, + max: PropTypes.number, + step: PropTypes.number, + disabled: PropTypes.bool, + placeholder: PropTypes.string, +}; + +NumericSection.defaultProps = { + value: undefined, + min: undefined, + max: undefined, + step: 1, + disabled: false, + placeholder: '', +}; + +export default NumericSection; diff --git a/frontend/frontend/src/components/Home/TextSection.js b/frontend/frontend/src/components/Home/TextSection.js new file mode 100644 index 00000000..de800568 --- /dev/null +++ b/frontend/frontend/src/components/Home/TextSection.js @@ -0,0 +1,63 @@ +/* eslint-disable react/no-danger */ + +import React, { useEffect, useRef, useState } from 'react'; +import PropTypes from 'prop-types'; + +import Section from './Section'; + +const TextSection = ({ + title, + text, + value, + setValue, + disabled, + placeholder, +}) => { + const [internalValue, setInternalValue] = useState(value); + const debounceTimeout = useRef(null); + + useEffect(() => { + // Debounce setValue + if (debounceTimeout.current) { + clearTimeout(debounceTimeout.current); + } + if (internalValue === value) { + return undefined; + } + debounceTimeout.current = setTimeout(() => { + setValue(internalValue); + }, 1500); + // return cleanup function: + return () => clearTimeout(debounceTimeout.current); + }, [internalValue]); + + return ( +
+

+ setInternalValue(e.target.value)} + disabled={disabled} + placeholder={placeholder} + /> +

+ ); +}; + +TextSection.propTypes = { + title: PropTypes.string.isRequired, + text: PropTypes.string.isRequired, + value: PropTypes.string.isRequired, + setValue: PropTypes.func.isRequired, + disabled: PropTypes.bool, + placeholder: PropTypes.string, +}; + +TextSection.defaultProps = { + disabled: false, + placeholder: '', +}; + +export default TextSection; diff --git a/frontend/frontend/src/pages/App/AppTrends.js b/frontend/frontend/src/pages/App/AppTrends.js index 0c8ebc5e..8bd300e7 100644 --- a/frontend/frontend/src/pages/App/AppTrends.js +++ b/frontend/frontend/src/pages/App/AppTrends.js @@ -1,14 +1,18 @@ /* eslint-disable jsx-a11y/anchor-is-valid */ import React, { useEffect } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { BrowserRouter as Router, - Routes, Route, + Routes, useParams, } from 'react-router-dom'; +import { + logout as _logout, + setPrivateAccess as _setPrivateAccess, +} from '../../redux/actions/userActions'; import Header from './Header'; import LandingScreen from '../Landing'; @@ -17,8 +21,6 @@ import { SignUpScreen } from '../Auth'; import HomeScreen from '../Home'; import SettingsScreen from '../Settings'; import { NoMatchScreen, RedirectScreen } from '../Misc'; - -import { setPrivateAccess as _setPrivateAccess } from '../../redux/actions/userActions'; import { getUserMetadata } from '../../api'; import { WRAPPED_URL } from '../../constants'; import Footer from './Footer'; @@ -52,6 +54,7 @@ function WrappedRedirectScreen() { function App() { const userId = useSelector((state) => state.user.userId); + const userKey = useSelector((state) => state.user.userKey); const isAuthenticated = userId && userId.length > 0; const dispatch = useDispatch(); @@ -59,15 +62,17 @@ function App() { useEffect(() => { async function getPrivateAccess() { - if (userId && userId.length > 0) { - const result = await getUserMetadata(userId); - if (result !== null && result.private_access !== undefined) { - setPrivateAccess(result.private_access); + if (userKey && userKey.length > 0) { + const privateAccess = await getUserMetadata(userKey); + if (privateAccess === null) { + dispatch(_logout()); + } else { + setPrivateAccess(privateAccess); } } } getPrivateAccess(); - }, [userId]); + }, [userKey]); return (
diff --git a/frontend/frontend/src/pages/App/Footer.js b/frontend/frontend/src/pages/App/Footer.js index 219bbd95..acf01c46 100644 --- a/frontend/frontend/src/pages/App/Footer.js +++ b/frontend/frontend/src/pages/App/Footer.js @@ -1,14 +1,24 @@ import React from 'react'; -import { CURR_YEAR } from '../../constants'; +import { FaGithub as GithubIcon } from 'react-icons/fa'; function Footer() { return ( diff --git a/frontend/frontend/src/pages/Demo/Demo.js b/frontend/frontend/src/pages/Demo/Demo.js index d9d07dbc..4ebba8b4 100644 --- a/frontend/frontend/src/pages/Demo/Demo.js +++ b/frontend/frontend/src/pages/Demo/Demo.js @@ -33,13 +33,13 @@ const DemoScreen = () => { const firstCardUrl = selectedUserName.length > 0 - ? `${BACKEND_URL}/user/svg/${selectedUserName}/langs?demo=true` - : `${BACKEND_URL}/user/svg/demo?card=langs`; + ? `https://github-readme-stats-phi-jet-58.vercel.app/api/?username=${selectedUserName}&client=demo` + : `${BACKEND_URL}/user/svg/demo?card=langs`; // TODO: placeholder image const secondCardUrl = selectedUserName.length > 0 - ? `${BACKEND_URL}/user/svg/${selectedUserName}/repos?demo=true` - : `${BACKEND_URL}/user/svg/demo?card=repos`; + ? `https://github-readme-stats-phi-jet-58.vercel.app/api/top-langs/?username=${selectedUserName}&client=demo` + : `${BACKEND_URL}/user/svg/demo?card=repos`; // TODO: placeholder image return (
diff --git a/frontend/frontend/src/pages/Home/Home.js b/frontend/frontend/src/pages/Home/Home.js index c6a4b61e..7110bf1e 100644 --- a/frontend/frontend/src/pages/Home/Home.js +++ b/frontend/frontend/src/pages/Home/Home.js @@ -1,25 +1,22 @@ import React, { useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; - -import { useNavigate } from 'react-router-dom'; import BounceLoader from 'react-spinners/BounceLoader'; -import { FaGithub as GithubIcon } from 'react-icons/fa'; +import { v4 as uuidv4 } from 'uuid'; import { ProgressBar } from '../../components'; import { - SelectCardStage, CustomizeStage, - ThemeStage, DisplayStage, + SelectCardStage, + ThemeStage, } from './stages'; -import { setUserKey, authenticate } from '../../api'; +import { authenticate } from '../../api'; import { login as _login } from '../../redux/actions/userActions'; import { PROD } from '../../constants'; +import { CardTypes } from '../../utils'; const HomeScreen = () => { - const navigate = useNavigate(); - const [isLoading, setIsLoading] = useState(false); const userId = useSelector((state) => state.user.userId); @@ -35,7 +32,8 @@ const HomeScreen = () => { const [stage, setStage] = useState(0); // for stage one - const [selectedCard, setSelectedCard] = useState('langs'); + const [selectedCard, setSelectedCard] = useState(); + const [imageSrc, setImageSrc] = useState(`?&username=${userId}`); // for stage two const defaultTimeRange = { @@ -53,6 +51,10 @@ const HomeScreen = () => { const [useLocChanged, setUseLocChanged] = useState(false); const [useCompact, setUseCompact] = useState(false); + const [showTitle, setShowTitle] = useState(true); + const [customTitle, setCustomTitle] = useState(''); + const [langsCount, setLangsCount] = useState(); + const resetCustomization = () => { setSelectedTimeRange(defaultTimeRange); setUsePercent(false); @@ -66,7 +68,7 @@ const HomeScreen = () => { }, [selectedCard]); const time = selectedTimeRange.value; - let fullSuffix = `${selectedCard}?time_range=${time}`; + let fullSuffix = `${imageSrc}&time_range=${time}`; if (usePercent) { fullSuffix += '&use_percent=True'; @@ -90,6 +92,19 @@ const HomeScreen = () => { fullSuffix += '&compact=True'; } + if (!showTitle) { + fullSuffix += '&hide_title=true'; + } + + if (customTitle) { + const encodedTitle = encodeURIComponent(customTitle); + fullSuffix += `&custom_title=${encodedTitle}`; + } + + if (langsCount) { + fullSuffix += `&langs_count=${langsCount}`; + } + // for stage three const [theme, setTheme] = useState('classic'); const themeSuffix = `${fullSuffix}&theme=${theme}`; @@ -99,10 +114,6 @@ const HomeScreen = () => { // After requesting Github access, Github redirects back to your app with a code parameter const url = window.location.href; - if (url.includes('error=')) { - navigate('/'); - } - // If Github API returns the code parameter if (url.includes('code=')) { const tempPrivateAccess = url.includes('private'); @@ -111,8 +122,12 @@ const HomeScreen = () => { const redirect = `${url.split(subStr)[0]}${subStr}/user`; window.history.pushState({}, null, redirect); setIsLoading(true); - const userKey = await setUserKey(newUrl[1]); - const newUserId = await authenticate(newUrl[1], tempPrivateAccess); + const userKey = uuidv4(); + const newUserId = await authenticate( + newUrl[1], + tempPrivateAccess, + userKey, + ); login(newUserId, userKey); setIsLoading(false); } @@ -181,11 +196,13 @@ const HomeScreen = () => { )} {stage === 1 && ( { setUsePercent={setUsePercent} useLocChanged={useLocChanged} setUseLocChanged={setUseLocChanged} + showTitle={showTitle} + setShowTitle={setShowTitle} + customTitle={customTitle} + setCustomTitle={setCustomTitle} + langsCount={langsCount} + setLangsCount={setLangsCount} fullSuffix={fullSuffix} /> )} @@ -216,21 +239,6 @@ const HomeScreen = () => { )}
-
- - - -
); }; diff --git a/frontend/frontend/src/pages/Home/stages/Customize.js b/frontend/frontend/src/pages/Home/stages/Customize.js index 574e6caa..136fb968 100644 --- a/frontend/frontend/src/pages/Home/stages/Customize.js +++ b/frontend/frontend/src/pages/Home/stages/Customize.js @@ -1,7 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Image, DateRangeSection, CheckboxSection } from '../../../components'; +import { CheckboxSection, DateRangeSection, Image } from '../../../components'; +import { CardTypes } from '../../../utils'; +import TextSection from '../../../components/Home/TextSection'; +import NumericSection from '../../../components/Home/NumericSection'; const CustomizeStage = ({ selectedCard, @@ -20,17 +23,52 @@ const CustomizeStage = ({ setUsePercent, useLocChanged, setUseLocChanged, + showTitle, + setShowTitle, + customTitle, + setCustomTitle, + langsCount, + setLangsCount, fullSuffix, }) => { + const cardType = selectedCard || CardTypes.STATS; return (
+ {cardType === CardTypes.STATS && ( + + )} + {cardType === CardTypes.STATS && ( + + )} + {cardType === CardTypes.TOP_LANGS && ( + + )} - {selectedCard === 'langs' && ( + {cardType === CardTypes.TOP_LANGS && ( - {selectedCard === 'repos' && ( + {cardType === CardTypes.STATS && ( )} - {selectedCard === 'repos' && usePrivate && groupOther && ( + {cardType === CardTypes.STATS && usePrivate && groupOther && ( )} - {selectedCard === 'langs' && ( + {cardType === CardTypes.TOP_LANGS && (
-
+
@@ -110,6 +148,12 @@ CustomizeStage.propTypes = { setUsePercent: PropTypes.func.isRequired, useLocChanged: PropTypes.bool.isRequired, setUseLocChanged: PropTypes.func.isRequired, + showTitle: PropTypes.bool.isRequired, + setShowTitle: PropTypes.func.isRequired, + customTitle: PropTypes.string.isRequired, + setCustomTitle: PropTypes.func.isRequired, + langsCount: PropTypes.number.isRequired, + setLangsCount: PropTypes.func.isRequired, fullSuffix: PropTypes.string.isRequired, }; diff --git a/frontend/frontend/src/pages/Home/stages/Display.js b/frontend/frontend/src/pages/Home/stages/Display.js index 3f7bc2ae..1521f714 100644 --- a/frontend/frontend/src/pages/Home/stages/Display.js +++ b/frontend/frontend/src/pages/Home/stages/Display.js @@ -15,15 +15,19 @@ const DisplayStage = ({ userId, themeSuffix }) => { const card = themeSuffix.split('?')[0]; const downloadPNG = () => { - saveSvgAsPng(document.getElementById('svg-card'), `${userId}_${card}.png`, { - scale: 2, - encoderOptions: 1, - }); + saveSvgAsPng( + document.getElementById('svg-card').firstElementChild, + `${userId}_${card}.png`, + { + scale: 2, + encoderOptions: 1, + }, + ); }; const copyUrl = () => { navigator.clipboard.writeText( - `https://api.githubtrends.io/user/svg/${userId}/${themeSuffix}`, + `https://github-readme-stats-phi-jet-58.vercel.app/api/${themeSuffix}&username=${userId}`, ); toast.info('Copied to Clipboard!', { position: 'bottom-right', @@ -70,7 +74,7 @@ const DisplayStage = ({ userId, themeSuffix }) => {
diff --git a/frontend/frontend/src/pages/Home/stages/SelectCard.js b/frontend/frontend/src/pages/Home/stages/SelectCard.js index 243ad7a1..da487913 100644 --- a/frontend/frontend/src/pages/Home/stages/SelectCard.js +++ b/frontend/frontend/src/pages/Home/stages/SelectCard.js @@ -2,35 +2,63 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { useSelector } from 'react-redux'; import { Card } from '../../../components'; -const SelectCardStage = ({ selectedCard, setSelectedCard }) => { +const SelectCardStage = ({ selectedCard, setSelectedCard, setImageSrc }) => { + const userId = useSelector((state) => state.user.userId); return (
{[ { - title: 'Language Contributions', - description: 'See your overall language breakdown', - imageSrc: 'langs', + title: 'GitHub Stats Card', + description: 'your overall GitHub statistics', + imageSrc: `?&username=${userId}`, + cardType: 'stats', }, { - title: 'Repository Contributions', - description: 'See your most contributed repositories', - imageSrc: 'repos', + title: 'Top Languages Card', + description: 'your most frequently used languages', + imageSrc: `top-langs/?&username=${userId}&langs_count=4`, + cardType: 'top-langs', + }, + { + title: 'GitHub Extra Pins', + description: + 'pin more than 6 repositories in your profile using a GitHub profile readme', + imageSrc: 'pin/?repo=anuraghazra/github-readme-stats', + cardType: 'pin', + }, + { + title: 'GitHub Gist Pins', + description: + 'pin gists in your GitHub profile using a GitHub profile readme', + imageSrc: 'gist/?id=bbfce31e0217a3689c8d961a356cb10d', + cardType: 'gist', + }, + { + title: 'WakaTime Stats Card', + description: 'your coding activity from WakaTime', + imageSrc: 'wakatime/?username=ffflabs&langs_count=6&card_width=450', + cardType: 'wakatime', }, ].map((card, index) => ( ))} @@ -39,8 +67,13 @@ const SelectCardStage = ({ selectedCard, setSelectedCard }) => { }; SelectCardStage.propTypes = { - selectedCard: PropTypes.string.isRequired, + selectedCard: PropTypes.string, setSelectedCard: PropTypes.func.isRequired, + setImageSrc: PropTypes.func.isRequired, +}; + +SelectCardStage.defaultProps = { + selectedCard: null, }; export default SelectCardStage; diff --git a/frontend/frontend/src/pages/Home/stages/Theme.js b/frontend/frontend/src/pages/Home/stages/Theme.js index 52939cab..25d2a675 100644 --- a/frontend/frontend/src/pages/Home/stages/Theme.js +++ b/frontend/frontend/src/pages/Home/stages/Theme.js @@ -35,7 +35,7 @@ const ThemeStage = ({ theme, setTheme, fullSuffix }) => { }, ].map((card, index) => ( + + + ) : (