apply all github-trends adaptions so far
taken from https://github.com/martin-mfg/github-trends/tree/grs-adaptions
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
REACT_APP_PROD=false
|
||||
PROD=false
|
||||
|
||||
REACT_APP_CLIENT_ID=abc123
|
||||
REACT_APP_DEV_CLIENT_ID=abc123
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user