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 (
+
+ );
+};
+
+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 (
+
+ );
+};
+
+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/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) => (