apply all github-trends adaptions so far

taken from https://github.com/martin-mfg/github-trends/tree/grs-adaptions
This commit is contained in:
martin-mfg
2025-09-18 13:37:10 +02:00
parent d149e0e1f5
commit 182f48e20e
24 changed files with 382 additions and 113 deletions
@@ -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)
@@ -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, [])
@@ -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
@@ -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()
@@ -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()
+1 -1
View File
@@ -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
+2 -2
View File
@@ -1,3 +1,3 @@
REACT_APP_PROD=false
PROD=false
REACT_APP_CLIENT_ID=abc123
REACT_APP_DEV_CLIENT_ID=abc123
+7 -7
View File
@@ -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) {
+22 -14
View File
@@ -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 (
<div className="relative h-full w-full relative">
<SVG
className="object-cover h-full w-full"
url={fullImageSrc}
compact={compact}
/>
<div className={`${extraClasses} relative w-full relative`}>
<SVG className="object-cover" url={fullImageSrc} compact={compact} />
</div>
);
};
@@ -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 (
<div
className={classnames(
'p-6 rounded border-2',
fixedSize ? 'h-[370px] w-[510px]' : '',
selected
? 'border-blue-500 bg-blue-50'
: 'border-gray-200 bg-white hover:bg-gray-50',
@@ -43,7 +45,11 @@ export const Card = ({ title, description, imageSrc, selected, compact }) => {
>
<h2 className="text-xl font-medium title-font text-gray-900">{title}</h2>
<p className="text-base leading-relaxed mt-2 mb-4">{description}</p>
<Image imageSrc={imageSrc} compact={compact} />
<Image
imageSrc={imageSrc}
compact={compact}
extraClasses={fixedSize ? 'flex justify-center' : ''}
/>
</div>
);
};
@@ -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,
};
+3 -6
View File
@@ -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) => {
<div
className={props.className}
dangerouslySetInnerHTML={{
__html: `<svg id="svg-card" viewBox="0 0 300 175">${svg}</svg>`,
__html: `<div id="svg-card">${svg}</div>`,
}}
/>
);
@@ -49,7 +46,7 @@ const SvgInline = (props) => {
<div
className={props.className}
dangerouslySetInnerHTML={{
__html: `<svg id="svg-card" viewBox="0 0 300 285">${svg}</svg>`,
__html: `<div id="svg-card">${svg}</div>`,
}}
/>
);
@@ -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 (
<Section title={title}>
<p>{text}</p>
<p dangerouslySetInnerHTML={{ __html: text }} />
<Checkbox
question={question}
variable={variable}
@@ -0,0 +1,79 @@
/* eslint-disable react/no-danger */
import React, { useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import Section from './Section';
const NumericSection = ({
title,
text,
value,
setValue,
min,
max,
step,
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);
}, 1000);
return () => clearTimeout(debounceTimeout.current);
}, [internalValue]);
useEffect(() => {
setInternalValue(value);
}, [value]);
return (
<Section title={title}>
<p dangerouslySetInnerHTML={{ __html: text }} />
<input
type="number"
className="border border-gray-300 rounded px-2 py-1 mt-2 w-1/4"
value={internalValue}
onChange={(e) => setInternalValue(e.target.value)}
min={min}
max={max}
step={step}
disabled={disabled}
placeholder={placeholder}
/>
</Section>
);
};
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;
@@ -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 (
<Section title={title}>
<p dangerouslySetInnerHTML={{ __html: text }} />
<input
type="text"
className="border border-gray-300 rounded px-2 py-1 mt-2 w-1/2"
value={internalValue}
onChange={(e) => setInternalValue(e.target.value)}
disabled={disabled}
placeholder={placeholder}
/>
</Section>
);
};
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;
+14 -9
View File
@@ -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 (
<div className="h-screen flex flex-col">
+15 -5
View File
@@ -1,14 +1,24 @@
import React from 'react';
import { CURR_YEAR } from '../../constants';
import { FaGithub as GithubIcon } from 'react-icons/fa';
function Footer() {
return (
<footer className="body-font">
<div className="bg-gray-100 border-t border-gray-300">
<div className="container mx-auto py-4 px-5">
<p className="text-gray-500 text-sm text-center">
{`© ${CURR_YEAR} GitHub Trends`}
</p>
<div className="container mx-auto py-4 px-5 flex justify-center">
<a
href="https://www.github.com/avgupta456/github-trends"
target="_blank"
rel="noopener noreferrer"
>
<button
type="button"
className="rounded-sm shadow bg-gray-700 hover:bg-gray-800 text-gray-50 px-3 py-2 flex items-center"
>
Star on
<GithubIcon className="ml-1.5 w-5 h-5" />
</button>
</a>
</div>
</div>
</footer>
+4 -4
View File
@@ -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 (
<div className="h-full py-8 flex flex-col xl:flex-row justify-center items-center">
+40 -32
View File
@@ -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 = () => {
<SelectCardStage
selectedCard={selectedCard}
setSelectedCard={setSelectedCard}
setImageSrc={setImageSrc}
/>
)}
{stage === 1 && (
<CustomizeStage
selectedCard={selectedCard}
selectedCard={selectedCard || CardTypes.STATS}
imageSrc={imageSrc}
selectedTimeRange={selectedTimeRange}
setSelectedTimeRange={setSelectedTimeRange}
usePrivate={usePrivate}
@@ -201,6 +218,12 @@ const HomeScreen = () => {
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 = () => {
)}
</div>
</div>
<div className="fixed bottom-8 right-8">
<a
href="https://www.github.com/avgupta456/github-trends"
target="_blank"
rel="noopener noreferrer"
>
<button
type="button"
className="rounded-sm shadow bg-gray-700 hover:bg-gray-800 text-gray-50 px-3 py-2 flex items-center"
>
Star on
<GithubIcon className="ml-1.5 w-5 h-5" />
</button>
</a>
</div>
</div>
);
};
@@ -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 (
<div className="w-full flex flex-wrap">
<div className="h-auto lg:w-2/5 md:w-1/2 pr-10 p-10 rounded-sm bg-gray-200">
{cardType === CardTypes.STATS && (
<CheckboxSection
title="Show Title?"
text="Shows a title at the top of the card."
question="Show title?"
variable={showTitle}
setVariable={setShowTitle}
/>
)}
{cardType === CardTypes.STATS && (
<TextSection
title="Custom Title"
text="Set a custom title for the card.<br>Leave empty for default title."
placeholder='e.g. "My GitHub Stats"'
value={customTitle}
setValue={setCustomTitle}
/>
)}
{cardType === CardTypes.TOP_LANGS && (
<NumericSection
title="Language Count"
text="Set the number of languages to be shown.<br>Leave empty for default count."
value={langsCount}
setValue={setLangsCount}
min={1}
max={20}
/>
)}
<DateRangeSection
selectedTimeRange={selectedTimeRange}
setSelectedTimeRange={setSelectedTimeRange}
privateAccess={privateAccess}
/>
{selectedCard === 'langs' && (
{cardType === CardTypes.TOP_LANGS && (
<CheckboxSection
title="Compact View"
text="Use default view or compact view."
@@ -47,7 +85,7 @@ const CustomizeStage = ({
setVariable={setUsePrivate}
disabled={!privateAccess}
/>
{selectedCard === 'repos' && (
{cardType === CardTypes.STATS && (
<CheckboxSection
title="Group Other Repositories?"
text="Group all remaining repositories together at the bottom of the card."
@@ -56,7 +94,7 @@ const CustomizeStage = ({
setVariable={setGroupOther}
/>
)}
{selectedCard === 'repos' && usePrivate && groupOther && (
{cardType === CardTypes.STATS && usePrivate && groupOther && (
<CheckboxSection
title="Group Private Repositories?"
text="Force private repositories together at the bottom of the card."
@@ -65,7 +103,7 @@ const CustomizeStage = ({
setVariable={setGroupPrivate}
/>
)}
{selectedCard === 'langs' && (
{cardType === CardTypes.TOP_LANGS && (
<CheckboxSection
title="Percent vs LOC"
text="Use absolute LOC (default) or percent to rank your top repositories"
@@ -81,11 +119,11 @@ const CustomizeStage = ({
question="Use LOC changed?"
variable={useLocChanged}
setVariable={setUseLocChanged}
disabled={selectedCard === 'langs' && usePercent}
disabled={cardType === CardTypes.TOP_LANGS && usePercent}
/>
</div>
<div className="w-full lg:w-3/5 md:w-1/2 object-center pt-5 md:pt-0 pl-0 md:pl-5 lg:pl-0">
<div className="w-full lg:w-3/5 mx-auto h-full flex flex-col justify-center">
<div className="w-full lg:w-3/5 mx-auto flex flex-col justify-center">
<Image imageSrc={fullSuffix} compact={useCompact} />
</div>
</div>
@@ -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,
};
@@ -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 }) => {
<Card
title="Your Card"
description="The finished product!"
imageSrc={`${themeSuffix}&use_animation=False`}
imageSrc={`${themeSuffix}&disable_animations=true`}
selected
/>
</div>
@@ -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 (
<div className="w-full flex flex-wrap">
{[
{
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) => (
<button
className="w-full sm:w-1/2 lg:w-1/3 p-2 lg:p-4"
className="p-2 lg:p-4"
key={index}
type="button"
onClick={() => setSelectedCard(card.imageSrc)}
onClick={() => {
setSelectedCard(card.cardType);
setImageSrc(card.imageSrc);
}}
>
<Card
title={card.title}
description={card.description}
imageSrc={card.imageSrc}
selected={selectedCard === card.imageSrc}
selected={selectedCard === card.cardType}
fixedSize="true"
/>
</button>
))}
@@ -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;
@@ -35,7 +35,7 @@ const ThemeStage = ({ theme, setTheme, fullSuffix }) => {
},
].map((card, index) => (
<button
className="w-full sm:w-1/2 lg:w-1/3 p-2 lg:p-4"
className="p-2 lg:p-4"
key={index}
type="button"
onClick={() => setTheme(card.imageSrc)}
@@ -4,7 +4,7 @@ import React from 'react';
import { useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import { FaGithub as GithubIcon, FaCheck as CheckIcon } from 'react-icons/fa';
import { FaCheck as CheckIcon, FaGithub as GithubIcon } from 'react-icons/fa';
import { Button, Preview } from '../../components';
@@ -1,15 +1,15 @@
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-static-element-interactions */
import React, { useState, useEffect, useRef } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { useSelector, useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { Button } from '../../components';
import { logout as _logout } from '../../redux/actions/userActions';
import { deleteAccount } from '../../api';
import { classnames } from '../../utils';
import { GITHUB_PRIVATE_AUTH_URL, CLIENT_ID } from '../../constants';
import { CLIENT_ID, GITHUB_PRIVATE_AUTH_URL } from '../../constants';
const SectionButton = ({ name, implemented, isSelected, setSelected }) => {
return (
@@ -167,9 +167,13 @@ const SettingsScreen = () => {
)}
<br />
{privateAccess ? (
<Button className="bg-gray-200 rounded-sm opacity-50 cursor-not-allowed">
Downgrade to Public Access
</Button>
<a
href={`https://github-readme-stats-phi-jet-58.vercel.app/api/downgrade?user_key=${userKey}`}
>
<Button className="bg-blue-500 text-white rounded-sm">
Downgrade to Public Access
</Button>
</a>
) : (
<a href={GITHUB_PRIVATE_AUTH_URL}>
<Button className="bg-blue-500 text-white rounded-sm">
+8
View File
@@ -6,3 +6,11 @@ export function sleep(ms) {
export function classnames(...args) {
return args.join(' ');
}
export const CardTypes = {
STATS: 'stats',
TOP_LANGS: 'top-langs',
PIN: 'pin',
GIST: 'gist',
WAKATIME: 'wakatime',
};