diff --git a/src/components/details/index.jsx b/src/components/details/index.jsx
index d84144a9d..5d0c340e3 100644
--- a/src/components/details/index.jsx
+++ b/src/components/details/index.jsx
@@ -1,13 +1,12 @@
-import { MdLocationOn, MdMail } from 'react-icons/md';
+import { MdLocationOn } from 'react-icons/md';
import {
AiFillGithub,
AiFillInstagram,
AiFillMediumSquare,
} from 'react-icons/ai';
import { SiTwitter } from 'react-icons/si';
-import { GrLinkedinOption } from 'react-icons/gr';
import { CgDribbble } from 'react-icons/cg';
-import { RiPhoneFill } from 'react-icons/ri';
+import { RiPhoneFill, RiMailFill } from 'react-icons/ri';
import { Fragment } from 'react';
import {
FaBehanceSquare,
@@ -15,11 +14,33 @@ import {
FaDev,
FaFacebook,
FaGlobe,
+ FaSkype,
+ FaMastodon,
FaStackOverflow,
+ FaTelegram,
+ FaLinkedin,
} from 'react-icons/fa';
import PropTypes from 'prop-types';
import { skeleton } from '../../helpers/utils';
+const isCompanyMention = (company) => {
+ return company.startsWith('@') && !company.includes(' ');
+};
+
+const companyLink = (company) => {
+ return `https://github.com/${company.substring(1)}`;
+};
+
+const getFormattedMastodonValue = (mastodonValue, isLink) => {
+ const [username, server] = mastodonValue.split('@');
+
+ if (isLink) {
+ return `https://${server}/@${username}`;
+ } else {
+ return `${username}@${server}`;
+ }
+};
+
const ListItem = ({ icon, title, value, link, skeleton = false }) => {
return (
{
rel="noreferrer"
className="flex justify-start py-2 px-1 items-center"
>
- {icon}
- {title}
+
+ {icon} {title}
+
);
};
-const isCompanyMention = (company) => {
- return company.startsWith('@') && !company.includes(' ');
-};
-
-const companyLink = (company) => {
- return `https://github.com/${company.substring(1)}`;
-};
-
const Details = ({ profile, loading, social, github }) => {
const renderSkeleton = () => {
let array = [];
@@ -83,14 +94,14 @@ const Details = ({ profile, loading, social, github }) => {
{profile.location && (
}
+ icon={}
title="Based in:"
value={profile.location}
/>
)}
{profile.company && (
}
+ icon={}
title="Company:"
value={profile.company}
link={
@@ -101,22 +112,30 @@ const Details = ({ profile, loading, social, github }) => {
/>
)}
}
+ icon={}
title="GitHub:"
value={github.username}
link={`https://github.com/${github.username}`}
/>
{social?.twitter && (
}
+ icon={}
title="Twitter:"
value={social.twitter}
link={`https://twitter.com/${social.twitter}`}
/>
)}
+ {social?.mastodon && (
+ }
+ title="Mastodon:"
+ value={getFormattedMastodonValue(social.mastodon, false)}
+ link={getFormattedMastodonValue(social.mastodon, true)}
+ />
+ )}
{social?.linkedin && (
}
+ icon={}
title="LinkedIn:"
value={social.linkedin}
link={`https://www.linkedin.com/in/${social.linkedin}`}
@@ -124,7 +143,7 @@ const Details = ({ profile, loading, social, github }) => {
)}
{social?.dribbble && (
}
+ icon={}
title="Dribbble:"
value={social.dribbble}
link={`https://dribbble.com/${social.dribbble}`}
@@ -132,7 +151,7 @@ const Details = ({ profile, loading, social, github }) => {
)}
{social?.behance && (
}
+ icon={}
title="Behance:"
value={social.behance}
link={`https://www.behance.net/${social.behance}`}
@@ -140,7 +159,7 @@ const Details = ({ profile, loading, social, github }) => {
)}
{social?.facebook && (
}
+ icon={}
title="Facebook:"
value={social.facebook}
link={`https://www.facebook.com/${social.facebook}`}
@@ -148,7 +167,7 @@ const Details = ({ profile, loading, social, github }) => {
)}
{social?.instagram && (
}
+ icon={}
title="Instagram:"
value={social.instagram}
link={`https://www.instagram.com/${social.instagram}`}
@@ -156,7 +175,7 @@ const Details = ({ profile, loading, social, github }) => {
)}
{social?.medium && (
}
+ icon={}
title="Medium:"
value={social.medium}
link={`https://medium.com/@${social.medium}`}
@@ -164,7 +183,7 @@ const Details = ({ profile, loading, social, github }) => {
)}
{social?.dev && (
}
+ icon={}
title="Dev:"
value={social.dev}
link={`https://dev.to/${social.dev}`}
@@ -172,7 +191,7 @@ const Details = ({ profile, loading, social, github }) => {
)}
{social?.stackoverflow && (
}
+ icon={}
title="Stack Overflow:"
value={social.stackoverflow.split('/').slice(-1)}
link={`https://stackoverflow.com/users/${social.stackoverflow}`}
@@ -180,15 +199,32 @@ const Details = ({ profile, loading, social, github }) => {
)}
{social?.website && (
}
+ icon={}
title="Website:"
value={social.website}
link={social.website}
/>
)}
+ {social?.skype && (
+ }
+ title="Skype"
+ value={social.skype}
+ link={`skype:${social.skype}?chat`}
+ />
+ )}
+ {social?.telegram && (
+ }
+ title="Telegram"
+ value={social.telegram}
+ link={`https://t.me/${social.telegram}`}
+ />
+ )}
+
{social?.phone && (
}
+ icon={}
title="Phone:"
value={social.phone}
link={`tel:${social.phone}`}
@@ -196,7 +232,7 @@ const Details = ({ profile, loading, social, github }) => {
)}
{social?.email && (
}
+ icon={}
title="Email:"
value={social.email}
link={`mailto:${social.email}`}
diff --git a/src/components/external-project/index.jsx b/src/components/external-project/index.jsx
new file mode 100644
index 000000000..3221d25df
--- /dev/null
+++ b/src/components/external-project/index.jsx
@@ -0,0 +1,171 @@
+import { Fragment } from 'react';
+import PropTypes from 'prop-types';
+import { ga, skeleton } from '../../helpers/utils';
+import LazyImage from '../lazy-image';
+
+const displaySection = (externalProjects) => {
+ if (
+ externalProjects &&
+ Array.isArray(externalProjects) &&
+ externalProjects.length
+ ) {
+ return true;
+ } else {
+ return false;
+ }
+};
+
+const ExternalProject = ({ externalProjects, loading, googleAnalytics }) => {
+ const renderSkeleton = () => {
+ let array = [];
+ for (let index = 0; index < externalProjects.length; index++) {
+ array.push(
+
+
+
+
+
+
+
+ {skeleton({
+ width: 'w-32',
+ height: 'h-8',
+ className: 'mb-2 mx-auto',
+ })}
+
+
+
+ {skeleton({
+ width: 'w-full',
+ height: 'h-full',
+ shape: '',
+ })}
+
+
+
+ {skeleton({
+ width: 'w-full',
+ height: 'h-4',
+ className: 'mx-auto',
+ })}
+
+
+ {skeleton({
+ width: 'w-full',
+ height: 'h-4',
+ className: 'mx-auto',
+ })}
+
+
+
+
+
+
+
+ );
+ }
+
+ return array;
+ };
+
+ const renderExternalProjects = () => {
+ return externalProjects.map((item, index) => (
+ {
+ e.preventDefault();
+
+ try {
+ if (googleAnalytics?.id) {
+ ga.event({
+ action: 'Click External Project',
+ params: {
+ post: item.title,
+ },
+ });
+ }
+ } catch (error) {
+ console.error(error);
+ }
+
+ window?.open(item.link, '_blank');
+ }}
+ >
+
+
+
+
+
+
+ {item.title}
+
+ {item.imageUrl && (
+
+ )}
+
+ {item.description}
+
+
+
+
+
+
+
+ ));
+ };
+
+ return (
+
+ {displaySection(externalProjects) && (
+
+
+
+
+
+
+
+ {loading ? (
+ skeleton({ width: 'w-40', height: 'h-8' })
+ ) : (
+
+ My Projects
+
+ )}
+
+
+
+
+ {loading ? renderSkeleton() : renderExternalProjects()}
+
+
+
+
+
+
+
+ )}
+
+ );
+};
+
+ExternalProject.propTypes = {
+ externalProjects: PropTypes.array,
+ loading: PropTypes.bool.isRequired,
+ googleAnalytics: PropTypes.object,
+};
+
+export default ExternalProject;
diff --git a/src/components/footer/index.jsx b/src/components/footer/index.jsx
new file mode 100644
index 000000000..fa4c62c93
--- /dev/null
+++ b/src/components/footer/index.jsx
@@ -0,0 +1,24 @@
+import PropTypes from 'prop-types';
+
+import { skeleton } from '../../helpers/utils';
+
+const Footer = ({ content, loading }) => {
+ if (!content) return null;
+
+ return (
+
+ {loading ? (
+ skeleton({ width: 'w-52', height: 'h-6' })
+ ) : (
+
+ )}
+
+ );
+};
+
+Footer.propTypes = {
+ content: PropTypes.string,
+ loading: PropTypes.bool.isRequired,
+};
+
+export default Footer;
diff --git a/src/components/project/index.jsx b/src/components/project/index.jsx
index 06d204e46..e46a0caae 100644
--- a/src/components/project/index.jsx
+++ b/src/components/project/index.jsx
@@ -1,6 +1,7 @@
-import { Fragment } from 'react';
-import { AiOutlineStar, AiOutlineFork } from 'react-icons/ai';
import PropTypes from 'prop-types';
+import { Fragment } from 'react';
+import { AiOutlineFork, AiOutlineStar } from 'react-icons/ai';
+import { MdInsertLink } from 'react-icons/md';
import { ga, languageColor, skeleton } from '../../helpers/utils';
const Project = ({ repo, loading, github, googleAnalytics }) => {
@@ -18,7 +19,11 @@ const Project = ({ repo, loading, github, googleAnalytics }) => {
- {skeleton({ width: 'w-32', height: 'h-8' })}
+ {skeleton({
+ width: 'w-32',
+ height: 'h-8',
+ className: 'mb-1',
+ })}
@@ -81,31 +86,17 @@ const Project = ({ repo, loading, github, googleAnalytics }) => {
>
-
-
-
-
- {item.name}
-
-
+
{item.description}
-
+
@@ -141,10 +132,10 @@ const Project = ({ repo, loading, github, googleAnalytics }) => {
{loading ? (
- skeleton({ width: 'w-28', height: 'h-8' })
+ skeleton({ width: 'w-40', height: 'h-8' })
) : (
- My Projects
+ GitHub Projects
)}
@@ -155,7 +146,7 @@ const Project = ({ repo, loading, github, googleAnalytics }) => {
href={`https://github.com/${github.username}?tab=repositories`}
target="_blank"
rel="noreferrer"
- className="text-base-content opacity-50"
+ className="text-base-content opacity-50 hover:underline"
>
See All
diff --git a/src/components/theme-changer/index.jsx b/src/components/theme-changer/index.jsx
index d4fb7f5a9..393281be5 100644
--- a/src/components/theme-changer/index.jsx
+++ b/src/components/theme-changer/index.jsx
@@ -57,7 +57,7 @@ const ThemeChanger = ({ theme, setTheme, loading, themeConfig }) => {
{[
diff --git a/src/helpers/utils.jsx b/src/helpers/utils.jsx
index c6e8acb99..bb7af3361 100644
--- a/src/helpers/utils.jsx
+++ b/src/helpers/utils.jsx
@@ -147,6 +147,7 @@ export const sanitizeConfig = (config) => {
social: {
linkedin: config?.social?.linkedin,
twitter: config?.social?.twitter,
+ mastodon: config?.social?.mastodon,
facebook: config?.social?.facebook,
instagram: config?.social?.instagram,
dribbble: config?.social?.dribbble,
@@ -157,11 +158,14 @@ export const sanitizeConfig = (config) => {
website: config?.social?.website,
phone: config?.social?.phone,
email: config?.social?.email,
+ skype: config?.social?.skype,
+ telegram: config?.social?.telegram,
},
resume: {
fileUrl: config?.resume?.fileUrl || '',
},
skills: config?.skills || [],
+ externalProjects: config?.externalProjects || [],
experiences: config?.experiences || [],
certifications: config?.certifications || [],
education: config?.education || [],
@@ -186,6 +190,7 @@ export const sanitizeConfig = (config) => {
themes: themes,
customTheme: customTheme,
},
+ footer: config?.footer,
};
};
@@ -207,7 +212,7 @@ export const tooManyRequestError = (reset) => {
target="_blank"
rel="noopener noreferrer"
>
- rate limit.
+ rate limit
! Try again later{` ${reset}`}.
diff --git a/types/index.d.ts b/types/index.d.ts
index 6278739ed..96a87d567 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -49,6 +49,11 @@ export interface Social {
*/
twitter?: string;
+ /**
+ * Mastodon
+ */
+ mastodon?: string;
+
/**
* Facebook
*/
@@ -89,6 +94,16 @@ export interface Social {
*/
website?: string;
+ /**
+ * Skype username
+ */
+ skype?: string;
+
+ /**
+ * Telegram username
+ */
+ telegram?: string;
+
/**
* Phone
*/
@@ -221,6 +236,13 @@ export interface Certifications {
link?: string;
}
+export interface ExternalProjects {
+ title: string;
+ description: string;
+ imageUrl?: string;
+ link: string;
+}
+
export interface Education {
institution?: string;
degree?: string;
@@ -258,6 +280,11 @@ export interface Config {
*/
experiences?: Array;
+ /**
+ * External Projects
+ */
+ externalProjects?: Array;
+
/**
* Certifications list
*/
@@ -287,6 +314,11 @@ export interface Config {
* Theme config
*/
themeConfig?: ThemeConfig;
+
+ /**
+ * Custom footer
+ */
+ footer?: string;
}
export interface GitProfileProps {