From f75eae45472638f4a526ec612e8d74884b1c2455 Mon Sep 17 00:00:00 2001 From: Ariful Alam Date: Sun, 27 Mar 2022 01:56:05 +0600 Subject: [PATCH] Sanitize config props --- src/components/GitProfile.jsx | 230 ++++++++---------- src/components/head-tag-editor/index.jsx | 2 +- src/components/theme-changer/index.jsx | 2 +- src/helpers/utils.jsx | 293 +++++++++++++++++------ types/index.d.ts | 44 ++-- 5 files changed, 346 insertions(+), 225 deletions(-) diff --git a/src/components/GitProfile.jsx b/src/components/GitProfile.jsx index b02ad6d..274ede9 100644 --- a/src/components/GitProfile.jsx +++ b/src/components/GitProfile.jsx @@ -12,9 +12,13 @@ import Education from './education'; import Project from './project'; import Blog from './blog'; import { - constructConfigWithMissingValues, + genericError, getInitialTheme, + noConfigError, + notFoundError, setupHotjar, + tooManyRequestError, + sanitizeConfig, } from '../helpers/utils'; import { HelmetProvider } from 'react-helmet-async'; import PropTypes from 'prop-types'; @@ -22,37 +26,31 @@ import '../assets/index.css'; const GitProfile = ({ config }) => { const [error, setError] = useState( - typeof config === 'undefined' - ? { - status: 500, - title: 'No Config is provided', - subTitle: 'Pass the required config as prop.', - } - : null + typeof config === 'undefined' && !config ? noConfigError : null ); - - typeof config !== 'undefined' && constructConfigWithMissingValues(config); - - const [theme, setTheme] = useState( - config ? getInitialTheme(config.themeConfig) : null + const [sanitizedConfig] = useState( + typeof config === 'undefined' && !config ? null : sanitizeConfig(config) ); + const [theme, setTheme] = useState(null); const [loading, setLoading] = useState(true); const [profile, setProfile] = useState(null); const [repo, setRepo] = useState(null); useEffect(() => { - if (theme) { - document.documentElement.setAttribute('data-theme', theme); + if (sanitizedConfig) { + setTheme(getInitialTheme(sanitizedConfig.themeConfig)); + setupHotjar(sanitizedConfig.hotjar); + loadData(); } - }, [theme]); + }, [sanitizedConfig]); useEffect(() => { - config && setupHotjar(config.hotjar); - }, []); + theme && document.documentElement.setAttribute('data-theme', theme); + }, [theme]); const loadData = useCallback(() => { axios - .get(`https://api.github.com/users/${config.github.username}`) + .get(`https://api.github.com/users/${sanitizedConfig.github.username}`) .then((response) => { let data = response.data; @@ -69,14 +67,15 @@ const GitProfile = ({ config }) => { .then(() => { let excludeRepo = ``; - config.github.exclude.projects.forEach((project) => { - excludeRepo += `+-repo:${config.github.username}/${project}`; + sanitizedConfig.github.exclude.projects.forEach((project) => { + excludeRepo += `+-repo:${sanitizedConfig.github.username}/${project}`; }); - let query = `user:${config.github.username}+fork:${!config.github - .exclude.forks}${excludeRepo}`; + let query = `user:${ + sanitizedConfig.github.username + }+fork:${!sanitizedConfig.github.exclude.forks}${excludeRepo}`; - let url = `https://api.github.com/search/repositories?q=${query}&sort=${config.github.sortBy}&per_page=${config.github.limit}&type=Repositories`; + let url = `https://api.github.com/search/repositories?q=${query}&sort=${sanitizedConfig.github.sortBy}&per_page=${sanitizedConfig.github.limit}&type=Repositories`; axios .get(url, { @@ -101,10 +100,6 @@ const GitProfile = ({ config }) => { }); }, [setLoading]); - useEffect(() => { - config && loadData(); - }, [loadData]); - const handleError = (error) => { console.error('Error:', error); try { @@ -113,57 +108,25 @@ const GitProfile = ({ config }) => { ).fromNow(); if (error.response.status === 403) { - setError({ - status: 429, - title: 'Too Many Requests.', - subTitle: ( -

- Oh no, you hit the{' '} - - rate limit. - - ! Try again later{` ${reset}`}. -

- ), - }); + setError(tooManyRequestError(reset)); } else if (error.response.status === 404) { - setError({ - status: 404, - title: 'The Github Username is Incorrect.', - subTitle: ( -

- Please provide correct github username in config. -

- ), - }); + setError(notFoundError); } else { - setError({ - status: 500, - title: 'Ops!!', - subTitle: 'Something went wrong.', - }); + setError(genericError); } } catch (error2) { - setError({ - status: 500, - title: 'Ops!!', - subTitle: 'Something went wrong.', - }); + setError(genericError); } }; return ( - {!error && ( + {sanitizedConfig && ( )}
@@ -174,84 +137,81 @@ const GitProfile = ({ config }) => { subTitle={error.subTitle} /> ) : ( - -
-
-
-
- {!error && !config.themeConfig.disableSwitch && ( - +
+
+
+
+ {!sanitizedConfig.themeConfig.disableSwitch && ( + + )} + +
+ - )} - -
- {typeof config.skills !== 'undefined' && ( - - )} - {typeof config.experiences !== 'undefined' && ( - )} - {typeof config.education !== 'undefined' && ( - )} +
-
-
-
- - {typeof config.blog !== 'undefined' && ( +
+
+ - )} +
-
- {/* DO NOT REMOVE/MODIFY THE FOOTER. FOR MORE INFO https://github.com/arifszn/gitprofile#-please-read */} -
-
-
-
-

- Made with{' '} - - GitProfile - {' '} - and ❤️ -

+ {/* DO NOT REMOVE/MODIFY THE FOOTER. FOR MORE INFO https://github.com/arifszn/gitprofile#-please-read */} +
-
- + + + ) )}
@@ -265,8 +225,8 @@ GitProfile.propTypes = { sortBy: PropTypes.oneOf(['stars', 'updated']), limit: PropTypes.number, exclude: PropTypes.shape({ - forks: PropTypes.bool.isRequired, - projects: PropTypes.array.isRequired, + forks: PropTypes.bool, + projects: PropTypes.array, }), }).isRequired, social: PropTypes.shape({ @@ -311,11 +271,11 @@ GitProfile.propTypes = { snippetVersion: PropTypes.number, }), themeConfig: PropTypes.shape({ - defaultTheme: PropTypes.string.isRequired, - disableSwitch: PropTypes.bool.isRequired, - respectPrefersColorScheme: PropTypes.bool.isRequired, - themes: PropTypes.array.isRequired, - customTheme: PropTypes.object.isRequired, + defaultTheme: PropTypes.string, + disableSwitch: PropTypes.bool, + respectPrefersColorScheme: PropTypes.bool, + themes: PropTypes.array, + customTheme: PropTypes.object, }), }).isRequired, }; diff --git a/src/components/head-tag-editor/index.jsx b/src/components/head-tag-editor/index.jsx index bfab1ef..bd9f767 100644 --- a/src/components/head-tag-editor/index.jsx +++ b/src/components/head-tag-editor/index.jsx @@ -57,7 +57,7 @@ const HeadTagEditor = ({ profile, theme, googleAnalytics, social }) => { HeadTagEditor.propTypes = { profile: PropTypes.object, - theme: PropTypes.string.isRequired, + theme: PropTypes.string, googleAnalytics: PropTypes.object.isRequired, social: PropTypes.object.isRequired, }; diff --git a/src/components/theme-changer/index.jsx b/src/components/theme-changer/index.jsx index 0e5c92d..af92dcd 100644 --- a/src/components/theme-changer/index.jsx +++ b/src/components/theme-changer/index.jsx @@ -89,7 +89,7 @@ const ThemeChanger = ({ theme, setTheme, loading, themeConfig }) => { }; ThemeChanger.propTypes = { - theme: PropTypes.string.isRequired, + theme: PropTypes.string, setTheme: PropTypes.func.isRequired, loading: PropTypes.bool.isRequired, themeConfig: PropTypes.object.isRequired, diff --git a/src/helpers/utils.jsx b/src/helpers/utils.jsx index 3f59b15..d5a8b1e 100644 --- a/src/helpers/utils.jsx +++ b/src/helpers/utils.jsx @@ -98,58 +98,12 @@ export const setupHotjar = (hotjarConfig) => { } }; -export const constructConfigWithMissingValues = (config) => { - if (typeof config.github.sortBy === 'undefined') { - Object.assign(config.github, { sortBy: 'stars' }); - } - - if (typeof config.github.limit === 'undefined') { - Object.assign(config.github, { limit: 6 }); - } - - if (typeof config.github.exclude === 'undefined') { - Object.assign(config.github, { exclude: { forks: false, projects: [] } }); - } - - if (typeof config.themeConfig === 'undefined') { - const themeConfig = { - defaultTheme: 'corporate', - disableSwitch: false, - respectPrefersColorScheme: false, - themes: [ - 'light', - 'dark', - 'cupcake', - 'bumblebee', - 'emerald', - 'corporate', - 'synthwave', - 'retro', - 'cyberpunk', - 'valentine', - 'halloween', - 'garden', - 'forest', - 'aqua', - 'lofi', - 'pastel', - 'fantasy', - 'wireframe', - 'black', - 'luxury', - 'dracula', - 'cmyk', - 'autumn', - 'business', - 'acid', - 'lemonade', - 'night', - 'coffee', - 'winter', - 'procyon', - ], - customTheme: { - procyon: { +export const validateConfig = (config) => { + const customTheme = + typeof config.themeConfig !== 'undefined' && + typeof config.themeConfig.customTheme !== 'undefined' + ? config.themeConfig.customTheme + : { primary: '#fc055b', secondary: '#219aaf', accent: '#e8d03a', @@ -157,26 +111,219 @@ export const constructConfigWithMissingValues = (config) => { 'base-100': '#E3E3ED', '--rounded-box': '3rem', '--rounded-btn': '3rem', - }, + }; + + const themes = + typeof config.themeConfig !== 'undefined' && + typeof config.themeConfig.themes !== 'undefined' + ? config.themeConfig.themes + : [ + 'light', + 'dark', + 'cupcake', + 'bumblebee', + 'emerald', + 'corporate', + 'synthwave', + 'retro', + 'cyberpunk', + 'valentine', + 'halloween', + 'garden', + 'forest', + 'aqua', + 'lofi', + 'pastel', + 'fantasy', + 'wireframe', + 'black', + 'luxury', + 'dracula', + 'cmyk', + 'autumn', + 'business', + 'acid', + 'lemonade', + 'night', + 'coffee', + 'winter', + 'procyon', + ]; + + return { + github: { + username: config.github.username, + sortBy: + typeof config.github.sortBy !== 'undefined' + ? config.github.sortBy + : 'stars', + limit: + typeof config.github.limit !== 'undefined' ? config.github.limit : 8, + exclude: { + forks: + typeof config.github.exclude !== 'undefined' && + typeof config.github.exclude.forks !== 'undefined' + ? config.github.exclude.forks + : false, + projects: + typeof config.github.exclude !== 'undefined' && + typeof config.github.exclude.projects !== 'undefined' + ? config.github.exclude.projects + : [], }, - }; - - Object.assign(config, { themeConfig: themeConfig }); - } - - if (typeof config.googleAnalytics === 'undefined') { - const googleAnalytics = { - id: '', - }; - - Object.assign(config, { googleAnalytics: googleAnalytics }); - } - - if (typeof config.social === 'undefined') { - const social = {}; - - Object.assign(config, { social: social }); - } - - return config; + }, + social: { + linkedin: + typeof config.social !== 'undefined' && + typeof config.social.linkedin !== 'undefined' + ? config.social.linkedin + : '', + twitter: + typeof config.social !== 'undefined' && + typeof config.social.twitter !== 'undefined' + ? config.social.twitter + : '', + facebook: + typeof config.social !== 'undefined' && + typeof config.social.facebook !== 'undefined' + ? config.social.facebook + : '', + dribbble: + typeof config.social !== 'undefined' && + typeof config.social.dribbble !== 'undefined' + ? config.social.dribbble + : '', + behance: + typeof config.social !== 'undefined' && + typeof config.social.behance !== 'undefined' + ? config.social.behance + : '', + medium: + typeof config.social !== 'undefined' && + typeof config.social.medium !== 'undefined' + ? config.social.medium + : '', + devto: + typeof config.social !== 'undefined' && + typeof config.social.devto !== 'undefined' + ? config.social.devto + : '', + website: + typeof config.social !== 'undefined' && + typeof config.social.website !== 'undefined' + ? config.social.website + : '', + phone: + typeof config.social !== 'undefined' && + typeof config.social.phone !== 'undefined' + ? config.social.phone + : '', + email: + typeof config.social !== 'undefined' && + typeof config.social.email !== 'undefined' + ? config.social.email + : '', + }, + skills: typeof config.skills !== 'undefined' ? config.skills : [], + experiences: + typeof config.experiences !== 'undefined' ? config.experiences : [], + education: typeof config.education !== 'undefined' ? config.education : [], + blog: { + source: + typeof config.blog !== 'undefined' && + typeof config.blog.source !== 'undefined' + ? config.blog.source + : '', + username: + typeof config.blog !== 'undefined' && + typeof config.blog.username !== 'undefined' + ? config.blog.username + : '', + limit: + typeof config.blog !== 'undefined' && + typeof config.blog.limit !== 'undefined' + ? config.blog.limit + : 10, + }, + googleAnalytics: { + id: + typeof config.googleAnalytics !== 'undefined' && + typeof config.googleAnalytics.id !== 'undefined' + ? config.googleAnalytics.id + : '', + }, + hotjar: { + id: + typeof config.hotjar !== 'undefined' && + typeof config.hotjar.id !== 'undefined' + ? config.hotjar.id + : '', + snippetVersion: + typeof config.hotjar !== 'undefined' && + typeof config.hotjar.snippetVersion !== 'undefined' + ? config.hotjar.snippetVersion + : 6, + }, + themeConfig: { + defaultTheme: + typeof config.themeConfig !== 'undefined' && + typeof config.themeConfig.defaultTheme !== 'undefined' + ? config.themeConfig.defaultTheme + : themes[0], + disableSwitch: + typeof config.themeConfig !== 'undefined' && + typeof config.themeConfig.disableSwitch !== 'undefined' + ? config.themeConfig.disableSwitch + : false, + respectPrefersColorScheme: + typeof config.themeConfig !== 'undefined' && + typeof config.themeConfig.respectPrefersColorScheme !== 'undefined' + ? config.themeConfig.respectPrefersColorScheme + : false, + themes: themes, + customTheme: customTheme, + }, + }; +}; + +export const noConfigError = { + status: 500, + title: 'No Config is provided.', + subTitle: 'Pass the required config as prop.', +}; + +export const tooManyRequestError = (reset) => { + return { + status: 429, + title: 'Too Many Requests.', + subTitle: ( +

+ Oh no, you hit the{' '} + + rate limit. + + ! Try again later{` ${reset}`}. +

+ ), + }; +}; + +export const notFoundError = { + status: 404, + title: 'The Github Username is Incorrect.', + subTitle: ( +

+ Please provide correct github username in config. +

+ ), +}; + +export const genericError = { + status: 500, + title: 'Ops!!', + subTitle: 'Something went wrong.', }; diff --git a/types/index.d.ts b/types/index.d.ts index 500d85a..cdde22a 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -27,14 +27,14 @@ export interface Github { /** * Forked projects will not be displayed if set to true */ - forks: boolean; + forks?: boolean; /** * These projects will not be displayed * * example: ['my-project1', 'my-project2'] */ - projects: Array; + projects?: Array; }; } @@ -94,65 +94,79 @@ export interface Blog { /** * medium | dev.to */ - source: string; + source?: string; /** * Username */ - username: string; + username?: string; /** * How many posts to display * * Max is 10 */ - limit: number; + limit?: number; } export interface GoogleAnalytics { /** * GA3 tracking id/GA4 tag id UA-XXXXXXXXX-X | G-XXXXXXXXXX */ - id: string; + id?: string; } export interface Hotjar { /** * Hotjar id */ - id: string; + id?: string; /** * Snippet Version */ - snippetVersion: number; + snippetVersion?: number; } export interface ThemeConfig { /** * Default theme */ - defaultTheme: string; + defaultTheme?: string; /** * Hides the switch in the navbar */ - disableSwitch: boolean; + disableSwitch?: boolean; /** * Should use the prefers-color-scheme media-query */ - respectPrefersColorScheme: boolean; + respectPrefersColorScheme?: boolean; /** * Available themes */ - themes: Array; + themes?: Array; /** * Custom theme */ - customTheme: object; + customTheme?: object; +} + +export interface Experience { + company?: string; + position?: string; + from?: string; + to?: string; +} + +export interface Education { + institution?: string; + degree?: string; + from?: string; + to?: string; } export interface Config { @@ -174,12 +188,12 @@ export interface Config { /** * Experience list */ - experiences?: Array; + experiences?: Array; /** * Education list */ - education?: Array; + education?: Array; /** * Blog config