profile/src/components/GitProfile.jsx
2023-01-03 14:23:03 +06:00

342 lines
11 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import axios from 'axios';
import { Fragment, useCallback, useEffect, useState } from 'react';
import HeadTagEditor from './head-tag-editor';
import ErrorPage from './error-page';
import ThemeChanger from './theme-changer';
import AvatarCard from './avatar-card';
import Details from './details';
import Skill from './skill';
import Experience from './experience';
import Certification from './certification';
import Education from './education';
import Project from './project';
import Blog from './blog';
import {
genericError,
getInitialTheme,
noConfigError,
notFoundError,
setupHotjar,
tooManyRequestError,
sanitizeConfig,
skeleton,
} from '../helpers/utils';
import { HelmetProvider } from 'react-helmet-async';
import PropTypes from 'prop-types';
import '../assets/index.css';
import { formatDistance } from 'date-fns';
import ExternalProject from './external-project';
const bgColor = 'bg-base-300';
const GitProfile = ({ config }) => {
const [error, setError] = useState(
typeof config === 'undefined' && !config ? noConfigError : 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 (sanitizedConfig) {
setTheme(getInitialTheme(sanitizedConfig.themeConfig));
setupHotjar(sanitizedConfig.hotjar);
loadData();
}
}, [sanitizedConfig]);
useEffect(() => {
theme && document.documentElement.setAttribute('data-theme', theme);
}, [theme]);
const loadData = useCallback(() => {
axios
.get(`https://api.github.com/users/${sanitizedConfig.github.username}`)
.then((response) => {
let data = response.data;
let profileData = {
avatar: data.avatar_url,
name: data.name ? data.name : '',
bio: data.bio ? data.bio : '',
location: data.location ? data.location : '',
company: data.company ? data.company : '',
};
setProfile(profileData);
return data;
})
.then((userData) => {
let excludeRepo = ``;
if (userData.public_repos === 0) {
setRepo([]);
return;
}
sanitizedConfig.github.exclude.projects.forEach((project) => {
excludeRepo += `+-repo:${sanitizedConfig.github.username}/${project}`;
});
let query = `user:${
sanitizedConfig.github.username
}+fork:${!sanitizedConfig.github.exclude.forks}${excludeRepo}`;
let url = `https://api.github.com/search/repositories?q=${query}&sort=${sanitizedConfig.github.sortBy}&per_page=${sanitizedConfig.github.limit}&type=Repositories`;
axios
.get(url, {
headers: {
'Content-Type': 'application/vnd.github.v3+json',
},
})
.then((response) => {
let data = response.data;
setRepo(data.items);
})
.catch((error) => {
handleError(error);
});
})
.catch((error) => {
handleError(error);
})
.finally(() => {
setLoading(false);
});
}, [setLoading]);
const handleError = (error) => {
console.error('Error:', error);
try {
let reset = formatDistance(
new Date(error.response.headers['x-ratelimit-reset'] * 1000),
new Date(),
{
addSuffix: true,
}
);
if (error.response.status === 403) {
setError(tooManyRequestError(reset));
} else if (error.response.status === 404) {
setError(notFoundError);
} else {
setError(genericError);
}
} catch (error2) {
setError(genericError);
}
};
return (
<HelmetProvider>
{sanitizedConfig && (
<HeadTagEditor
profile={profile}
theme={theme}
googleAnalytics={sanitizedConfig.googleAnalytics}
social={sanitizedConfig.social}
/>
)}
<div className="fade-in h-screen">
{error ? (
<ErrorPage
status={`${error.status}`}
title={error.title}
subTitle={error.subTitle}
/>
) : (
sanitizedConfig && (
<Fragment>
<div className={`p-4 lg:p-10 min-h-full ${bgColor}`}>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 rounded-box">
<div className="col-span-1">
<div className="grid grid-cols-1 gap-6">
{!sanitizedConfig.themeConfig.disableSwitch && (
<ThemeChanger
theme={theme}
setTheme={setTheme}
loading={loading}
themeConfig={sanitizedConfig.themeConfig}
/>
)}
<AvatarCard
profile={profile}
loading={loading}
avatarRing={!sanitizedConfig.themeConfig.hideAvatarRing}
resume={sanitizedConfig.resume}
/>
<Details
profile={profile}
loading={loading}
github={sanitizedConfig.github}
social={sanitizedConfig.social}
/>
<Skill
loading={loading}
skills={sanitizedConfig.skills}
/>
<Experience
loading={loading}
experiences={sanitizedConfig.experiences}
/>
<Education
loading={loading}
education={sanitizedConfig.education}
/>
<Certification
loading={loading}
certifications={sanitizedConfig.certifications}
/>
</div>
</div>
<div className="lg:col-span-2 col-span-1">
<div className="grid grid-cols-1 gap-6">
<Project
repo={repo}
loading={loading}
github={sanitizedConfig.github}
googleAnalytics={sanitizedConfig.googleAnalytics}
/>
<ExternalProject
loading={loading}
externalProjects={sanitizedConfig.externalProjects}
googleAnalytics={sanitizedConfig.googleAnalytics}
/>
<Blog
loading={loading}
googleAnalytics={sanitizedConfig.googleAnalytics}
blog={sanitizedConfig.blog}
/>
</div>
</div>
</div>
</div>
<footer
className={`p-4 footer ${bgColor} text-base-content footer-center`}
>
<div className="card compact bg-base-100 shadow">
<a
className="card-body"
href="https://github.com/arifszn/gitprofile"
target="_blank"
rel="noreferrer"
>
<div>
{loading ? (
skeleton({ width: 'w-52', height: 'h-6' })
) : (
<p className="font-mono text-sm">
Made with{' '}
<span className="text-primary">GitProfile</span> and
</p>
)}
</div>
</a>
</div>
</footer>
</Fragment>
)
)}
</div>
</HelmetProvider>
);
};
GitProfile.propTypes = {
config: PropTypes.shape({
github: PropTypes.shape({
username: PropTypes.string.isRequired,
sortBy: PropTypes.oneOf(['stars', 'updated']),
limit: PropTypes.number,
exclude: PropTypes.shape({
forks: PropTypes.bool,
projects: PropTypes.array,
}),
}).isRequired,
social: PropTypes.shape({
linkedin: PropTypes.string,
twitter: PropTypes.string,
facebook: PropTypes.string,
instagram: PropTypes.string,
dribbble: PropTypes.string,
behance: PropTypes.string,
medium: PropTypes.string,
dev: PropTypes.string,
stackoverflow: PropTypes.string,
website: PropTypes.string,
phone: PropTypes.string,
email: PropTypes.string,
}),
skills: PropTypes.array,
externalProjects: PropTypes.arrayOf(
PropTypes.shape({
title: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
link: PropTypes.string.isRequired,
imageUrl: PropTypes.string,
})
),
experiences: PropTypes.arrayOf(
PropTypes.shape({
company: PropTypes.string,
position: PropTypes.string,
from: PropTypes.string,
to: PropTypes.string,
})
),
certifications: PropTypes.arrayOf(
PropTypes.shape({
body: PropTypes.string,
name: PropTypes.string,
year: PropTypes.string,
link: PropTypes.string,
})
),
education: PropTypes.arrayOf(
PropTypes.shape({
institution: PropTypes.string,
degree: PropTypes.string,
from: PropTypes.string,
to: PropTypes.string,
})
),
blog: PropTypes.shape({
source: PropTypes.string,
username: PropTypes.string,
limit: PropTypes.number,
}),
googleAnalytics: PropTypes.shape({
id: PropTypes.string,
}),
hotjar: PropTypes.shape({
id: PropTypes.string,
snippetVersion: PropTypes.number,
}),
themeConfig: PropTypes.shape({
defaultTheme: PropTypes.string,
disableSwitch: PropTypes.bool,
respectPrefersColorScheme: PropTypes.bool,
hideAvatarRing: PropTypes.bool,
themes: PropTypes.array,
customTheme: PropTypes.shape({
primary: PropTypes.string,
secondary: PropTypes.string,
accent: PropTypes.string,
neutral: PropTypes.string,
'base-100': PropTypes.string,
'--rounded-box': PropTypes.string,
'--rounded-btn': PropTypes.string,
}),
}),
}).isRequired,
};
export default GitProfile;