184 Commits
v1.1.1 ... v2

Author SHA1 Message Date
Ariful Alam
36097918fd Publish version 2.0.5 🚀 2022-05-14 21:18:27 +06:00
Ariful Alam
e5cc04ec57 Update README.md 2022-05-14 21:17:19 +06:00
Ariful Alam
b1fcdcf0c5 Merge pull request #73 from arifszn/71-failed-with-exit-code-128
build: apply explicit permissions
2022-05-14 04:45:15 +06:00
Ariful Alam
2b50dfcf6c Apply explicit permissions for contents 2022-05-14 04:44:09 +06:00
Ariful Alam
43b0bc91f0 Update gitprofile.config.js 2022-05-14 04:43:20 +06:00
Ariful Alam
6672d91d1c Remove step of workflow permission 2022-05-14 04:43:14 +06:00
Ariful Alam
aad79fa521 Update README.md 2022-05-11 13:13:21 +06:00
Ariful Alam
fb08deafd6 Merge pull request #72 from arifszn/docs
docs: update installation guide
2022-05-10 22:55:15 +06:00
Ariful Alam
7f73463863 Update installation guide 2022-05-10 22:54:19 +06:00
Ariful Alam
5983a95a9f Update gitprofile.config.js 2022-05-10 22:24:34 +06:00
Ariful Alam
ea9f8977b8 Fix typo 2022-05-10 21:31:31 +06:00
Ariful Alam
4b07170613 Merge pull request #68 from arifszn/card-shadow
feat: add shadow to project and blog cards
2022-04-24 14:48:38 +06:00
Ariful Alam
f6f38d908c Add shadow to project and blog cards 2022-04-24 14:46:48 +06:00
Ariful Alam
dcd1b56fe7 Merge pull request #67 from arifszn/gradient-direction
feat: change direction of gradient
2022-04-24 14:34:16 +06:00
Ariful Alam
166392a306 Change direction of gradient 2022-04-24 14:33:16 +06:00
Ariful Alam
5a4423e7fa Merge pull request #66 from arifszn/card-design
feat: display empty blog state
2022-04-24 14:22:15 +06:00
Ariful Alam
4800b0e1ae Bump version to 2.0.4 🚀 2022-04-24 14:19:51 +06:00
Ariful Alam
ff3d0f1abd Use gradient card instead of glass 2022-04-24 14:19:41 +06:00
Ariful Alam
035eb95723 Display empty blog state 2022-04-24 14:16:49 +06:00
Ariful Alam
0f3becb8f9 Merge pull request #63 from arifszn/scripts
build: update prettier script
2022-04-23 19:45:35 +06:00
Ariful Alam
9486c2b3ef Bump version to 2.0.3 🚀 2022-04-23 19:43:44 +06:00
Ariful Alam
bbb8911495 Update prettier script 2022-04-23 19:43:26 +06:00
Ariful Alam
f0849a1bbb Merge pull request #62 from arifszn/61-update-to-react-18
build: update to react 18
2022-04-23 19:35:39 +06:00
Ariful Alam
bbb7f41dd3 Update react to 18 2022-04-23 19:31:41 +06:00
Ariful Alam
d7194ecada Update dependencies 2022-04-23 17:45:52 +06:00
Ariful Alam
4b5ec65fe7 Merge pull request #59 from arifszn/update-dependencies
feat: use `blog-js` for fetching blog posts
2022-03-30 21:44:45 +06:00
Ariful Alam
36f9487eca Bump version to 2.0.2 🚀 2022-03-30 21:37:53 +06:00
Ariful Alam
5731e64819 Update README.md 2022-03-30 21:37:16 +06:00
Ariful Alam
66d680e2ec Rename dev.to 2022-03-30 21:30:42 +06:00
Ariful Alam
6f64574f5c Use blog-js for fetching blog posts 2022-03-30 21:18:57 +06:00
Ariful Alam
28abcd0570 Fix typo 2022-03-29 00:47:34 +06:00
Ariful Alam
aef59d1d68 Merge pull request #58 from arifszn/57-make-experience-and-education-sections-more-visible
feat: make experience and education sections more visible
2022-03-28 20:33:39 +06:00
Ariful Alam
5345d2af3d Bump version to 2.0.1 🚀 2022-03-28 20:31:47 +06:00
Ariful Alam
df281242e1 Make experience and education sections more visible 2022-03-28 20:31:02 +06:00
Ariful Alam
fabb4f67a7 Merge pull request #56 from arifszn/library
feat: npm package
2022-03-28 20:07:25 +06:00
Ariful Alam
665ee17be9 Change package link 2022-03-28 20:05:27 +06:00
Ariful Alam
296c2adc46 Create .npmignore 2022-03-28 19:55:58 +06:00
Ariful Alam
fb8f5e3bec Update dependencies 2022-03-28 19:49:01 +06:00
Ariful Alam
19955a44d0 Update README.md 2022-03-28 19:33:15 +06:00
Ariful Alam
7d9b1d9606 Support SSR 2022-03-28 18:49:35 +06:00
Ariful Alam
48f4920c1d Update gitprofile logo 2022-03-28 18:46:10 +06:00
Ariful Alam
8dbe879711 Update README.md 2022-03-27 21:47:11 +06:00
Ariful Alam
cdc66ea70b Change default blog posts limit to 5 2022-03-27 18:57:40 +06:00
Ariful Alam
851f761104 Update comment 2022-03-27 18:51:31 +06:00
Ariful Alam
6c2ca85bc9 Update README.md 2022-03-27 18:17:10 +06:00
Ariful Alam
54ff094b64 Add type definition for custom theme 2022-03-27 17:57:02 +06:00
Ariful Alam
e46529b25b Update footer 2022-03-27 14:55:25 +06:00
Ariful Alam
21f8b0633d Update Contributing 2022-03-27 13:52:56 +06:00
Ariful Alam
37618822b7 Fix function name 2022-03-27 02:10:01 +06:00
Ariful Alam
d00fa61b08 Remove custom theme node to support in package 2022-03-27 02:08:02 +06:00
Ariful Alam
f75eae4547 Sanitize config props 2022-03-27 01:56:05 +06:00
Ariful Alam
2743dee9aa Add type definition for all props 2022-03-26 18:26:04 +06:00
Ariful Alam
4cb107e168 Rename themeConfig.default to themeConfig.defaultTheme 2022-03-26 18:23:09 +06:00
Ariful Alam
59a9a2fac4 Add type definitions for social link 2022-03-26 18:09:04 +06:00
Ariful Alam
263984b0a4 Make optional configs 2022-03-26 18:07:14 +06:00
Ariful Alam
7794d543d3 Update files option for npm publish 2022-03-26 15:02:26 +06:00
Ariful Alam
2623da910b Add custom postcss config 2022-03-26 13:51:35 +06:00
Ariful Alam
277fe5b01c Rename package.config.js to library.config.js 2022-03-26 13:39:29 +06:00
Ariful Alam
9f24ddbe46 Add type definitions 2022-03-26 13:30:43 +06:00
Ariful Alam
8dedc09ea8 Update error page 2022-03-25 23:29:06 +06:00
Ariful Alam
82eb282e17 Pass config as props 2022-03-25 22:45:11 +06:00
Ariful Alam
c6066fd038 Support SSR 2022-03-25 18:47:28 +06:00
Ariful Alam
5058711985 Include style.css in exports 2022-03-25 18:45:30 +06:00
Ariful Alam
bbac82bd16 Remove custom build option 2022-03-24 23:36:26 +06:00
Ariful Alam
39125b018c Add build:package script 2022-03-24 23:34:19 +06:00
Ariful Alam
7964ae57bb Create package.config.js 2022-03-24 23:32:27 +06:00
Ariful Alam
b3715bca0a Move entry file to components folder 2022-03-24 23:26:36 +06:00
Ariful Alam
07e1e090a1 Test run 2022-03-24 22:22:45 +06:00
Ariful Alam
c453eb95fd Merge pull request #52 from arifszn/51-add-control-to-customize-the-button-style-on-custom-theme
feat: add control to customize the button style on custom theme
2022-03-23 18:52:16 +06:00
MD. Ariful Alam
9ac2e49a58 Add control to customize the button style on custom theme 2022-03-23 18:47:00 +06:00
Ariful Alam
24b2194129 Merge pull request #50 from arifszn/update-dependencies
build: update dependencies
2022-03-23 02:45:36 +06:00
Ariful Alam
d5bea5a160 Update dependencies 2022-03-23 02:43:21 +06:00
Ariful Alam
2209b21942 Merge pull request #49 from arifszn/47-add-ability-to-make-custom-theme
feat: add ability to make custom theme
2022-03-23 02:21:53 +06:00
Ariful Alam
725091dc52 Add custom theme option 2022-03-23 02:13:51 +06:00
Ariful Alam
4013491663 Add 8 new themes 2022-03-23 02:05:34 +06:00
Ariful Alam
4390701a8c Remove fix height of theme changer 2022-03-23 01:55:24 +06:00
Ariful Alam
ad2439316e Remove uppercase class from skill 2022-03-23 01:55:02 +06:00
Ariful Alam
a4b1660004 Remove gh-pages 2022-03-23 01:12:57 +06:00
Ariful Alam
4bbbf7cde3 Move gitprofile.config.js to root 2022-03-22 23:44:17 +06:00
Ariful Alam
b89dd276df Merge pull request #45 from arifszn/44-update-deploy-guide
Update deploy guide
2022-03-22 02:56:16 +06:00
Ariful Alam
7f007a8bb0 Update guide for deployment 2022-03-22 02:54:37 +06:00
Ariful Alam
741e8ff6cd Add instruction for base value in vite.config.js 2022-03-22 02:50:29 +06:00
Ariful Alam
ebc01a51f9 Remove homepage 2022-03-22 02:41:29 +06:00
Ariful Alam
f309fdb5a6 Remove manual deploy scripts 2022-03-22 02:41:12 +06:00
Ariful Alam
764fbea04b Merge pull request #43 from arifszn/42-combine-lint-and-test-deploy-actions
build: combine lint and test-deploy actions
2022-03-22 02:29:38 +06:00
Ariful Alam
e0b62bb38c Delete lint.yml 2022-03-22 02:23:38 +06:00
Ariful Alam
db3d5c363d Add lint and prettier step on test-deploy 2022-03-22 02:23:15 +06:00
Ariful Alam
f409069210 Fix getInitialTheme method to get theme from localStorage 2022-03-21 02:11:39 +06:00
Ariful Alam
da281dfc80 Set respectPrefersColorScheme to false 2022-03-21 02:02:57 +06:00
Ariful Alam
a40a6a669f Bump version to 2.0.0 🚀 2022-03-20 18:45:32 +06:00
Ariful Alam
d7fa99c787 Merge pull request #41 from arifszn/design
Update design of project and blog sections
2022-03-20 18:44:02 +06:00
Ariful Alam
1f74163548 Wrap the blog items with a container 2022-03-20 18:40:14 +06:00
Ariful Alam
ea71fdee1d Wrap the project items with a container 2022-03-20 18:34:20 +06:00
Ariful Alam
d719e38531 Merge pull request #40 from arifszn/issue-39
feat: make the project and blog items openable on right click action
2022-03-20 18:12:38 +06:00
Ariful Alam
f8fff21f3c Increase the project limit 2022-03-20 18:11:20 +06:00
Ariful Alam
e9ac566737 Make the blog items openable on right click action 2022-03-20 18:09:31 +06:00
Ariful Alam
0a9671bddc Make the project items openable on right click action 2022-03-20 18:05:48 +06:00
Ariful Alam
adc5fc857f Merge pull request #38 from arifszn/close-37
feat: check the value of `localStorage.getItem` exists in the theme array
2022-03-20 17:49:50 +06:00
Ariful Alam
ab3eb39618 Check the value of localStorage.getItem exists in the theme array 2022-03-20 17:47:03 +06:00
Ariful Alam
101e00845d Update README.md 2022-03-20 11:46:14 +06:00
Ariful Alam
f711238d73 Update gitprofile.config.js 2022-03-20 02:35:10 +06:00
Ariful Alam
fe21ead1f2 Merge pull request #36 from arifszn/update-repo-name
Update repo name
2022-03-20 02:25:36 +06:00
Ariful Alam
20ac254201 Update repo name 2022-03-20 02:23:44 +06:00
Ariful Alam
b864c2cdd7 Merge pull request #35 from arifszn/lint-ci-cd
Create CI/CD for automated lint and prettier checking
2022-03-20 02:00:25 +06:00
Ariful Alam
d1fb373ead Create lint.yml 2022-03-20 01:58:19 +06:00
Ariful Alam
42746ae8ef Merge pull request #33 from arifszn/test-deployment-ci-cd
Trigger pipeline of test deploy on pull_request instead of push
2022-03-20 01:55:58 +06:00
Ariful Alam
07d0b15460 Update test-deploy.yml 2022-03-20 01:54:21 +06:00
Ariful Alam
a6063253f2 Merge pull request #32 from arifszn/test-deployment-ci-cd
build: create CI/CD for test deployment
2022-03-20 01:53:25 +06:00
Ariful Alam
059dce3dbe Create CI/CD for test deployment 2022-03-20 01:52:37 +06:00
Ariful Alam
2ba2674a7f Merge pull request #30 from arifszn/add-contributing-guide
Add contributing guide
2022-03-20 01:47:08 +06:00
Ariful Alam
19243fde6a Update Contribute section 2022-03-20 01:46:39 +06:00
Ariful Alam
4ac53af497 Add contributing guide 2022-03-20 01:45:29 +06:00
Ariful Alam
b5a3cfa798 Merge pull request #29 from arifszn/add-code-of-conduct
Add code of conduct
2022-03-20 01:43:05 +06:00
Ariful Alam
b020acd1bc Add code of conduct 2022-03-20 01:42:30 +06:00
Ariful Alam
79c10e0e0b Merge pull request #28 from arifszn/Add-formatting-and-style-guide
build: add formatting and style guide
2022-03-20 01:39:15 +06:00
Ariful Alam
ec458191fb Validate missing prop types 2022-03-20 01:36:43 +06:00
Ariful Alam
a2ada305a7 Add scripts for lint and prettier 2022-03-20 01:25:23 +06:00
Ariful Alam
3354eb9f9b Create .prettierignore 2022-03-20 01:24:30 +06:00
Ariful Alam
b1ba17e17a Create .prettierrc 2022-03-20 01:24:07 +06:00
Ariful Alam
ddcb175234 Create .eslintignore 2022-03-20 01:23:40 +06:00
Ariful Alam
3529f39b7f Create .eslintrc.js 2022-03-20 01:23:00 +06:00
Ariful Alam
7866762a76 Install packages for lint and prettier 2022-03-20 01:21:24 +06:00
Ariful Alam
189e0f695e Make the whole item clickable of details 2022-03-20 01:19:17 +06:00
Ariful Alam
875ba4c899 Merge pull request #27 from arifszn/build/change-deploy-folder
build: define `base` in vite.config.js
2022-03-20 01:10:23 +06:00
Ariful Alam
c1068821a1 Define base in vite.config.js 2022-03-20 01:09:14 +06:00
Ariful Alam
8f45e648c1 Merge pull request #25 from arifszn/build/change-deploy-folder
build: change deploy folder for CI/CD
2022-03-20 00:58:32 +06:00
Ariful Alam
7da02cbceb Change deploy folder in deploy.yml 2022-03-20 00:56:07 +06:00
Ariful Alam
5c9f17d5f1 Merge pull request #23 from arifszn/migrate-to-vite
Migrate to vite.js
2022-03-20 00:50:47 +06:00
Ariful Alam
3a67c9ba31 Increase skill tag opacity 2022-03-20 00:43:40 +06:00
Ariful Alam
34e2d1da01 Change default theme to corporate 2022-03-20 00:43:35 +06:00
Ariful Alam
e91c8d9df0 Setup hotjar integration 2022-03-20 00:40:59 +06:00
Ariful Alam
2951f53fc0 Install react-hotjar 2022-03-20 00:33:56 +06:00
Ariful Alam
da1810adf0 Update footer style 2022-03-20 00:28:14 +06:00
Ariful Alam
c3360fa0f7 Update README.md 2022-03-20 00:22:17 +06:00
Ariful Alam
ab093235e3 Add class font-mono in bio 2022-03-20 00:18:55 +06:00
Ariful Alam
32c663fd55 Update style of blog 2022-03-20 00:17:18 +06:00
Ariful Alam
b4d65da1a7 Update style of right section 2022-03-20 00:17:15 +06:00
Ariful Alam
b4c81095e8 Update header 2022-03-20 00:16:27 +06:00
Ariful Alam
86ad16ee31 Update style of education history 2022-03-19 22:18:34 +06:00
Ariful Alam
fdffd280b7 Update style 2022-03-19 22:12:38 +06:00
Ariful Alam
7a935e9cb5 Remove redundant class bg-base-100 2022-03-19 20:27:23 +06:00
Ariful Alam
206ae1c8b6 Center the skill items 2022-03-19 20:23:56 +06:00
Ariful Alam
1565307200 Update skeleton style of details 2022-03-19 17:41:03 +06:00
Ariful Alam
ee36b5c49c Update details section 2022-03-19 17:17:25 +06:00
Ariful Alam
1a87e391b2 Add padding to list item's wrapper 2022-03-19 16:38:22 +06:00
Ariful Alam
ab55fa68f1 Remove context for state management 2022-03-19 16:34:13 +06:00
Ariful Alam
b69ced8ce0 Pass object instead of array in context provider 2022-03-19 16:16:47 +06:00
Ariful Alam
2b94b40bf3 Change style of details section 2022-03-19 15:50:29 +06:00
Ariful Alam
10238cc3a4 Add margin bottom in skeleton 2022-03-19 01:13:18 +06:00
Ariful Alam
d79bec006a Update utils.jsx 2022-03-19 00:55:08 +06:00
Ariful Alam
d76c4fd52d Add sections 2022-03-19 00:55:03 +06:00
Ariful Alam
6673428533 Update config 2022-03-19 00:54:50 +06:00
Ariful Alam
9b1e01b8df Add custom css 2022-03-19 00:54:37 +06:00
Ariful Alam
ce8a736106 Add packages in devDependencies 2022-03-19 00:54:27 +06:00
Ariful Alam
be3e0d1ffb Create ThemeChanger component 2022-03-19 00:53:45 +06:00
Ariful Alam
0ff1235735 Create Skill component 2022-03-19 00:53:37 +06:00
Ariful Alam
a533524604 Create project component 2022-03-19 00:53:29 +06:00
Ariful Alam
92bdc1acf6 Create LazyImage component 2022-03-19 00:53:20 +06:00
Ariful Alam
9d1de8173f Create HeadTagEditor component 2022-03-19 00:53:02 +06:00
Ariful Alam
c0d1a68123 Create Experience component 2022-03-19 00:52:51 +06:00
Ariful Alam
1fc3ca059a Create ErrorPage component 2022-03-19 00:52:38 +06:00
Ariful Alam
fce65c18e2 Create Education component 2022-03-19 00:52:31 +06:00
Ariful Alam
aaf7e4a8cf Create Details component 2022-03-19 00:52:23 +06:00
Ariful Alam
a3db9fddb3 Create Blog component 2022-03-19 00:52:17 +06:00
Ariful Alam
d47bbe079c Create AvatarCard 2022-03-19 00:52:00 +06:00
Ariful Alam
87d92c4c91 Rename file extension 2022-03-19 00:15:21 +06:00
Ariful Alam
e47a6f722e Add ezprofile.config.js 2022-03-19 00:15:00 +06:00
Ariful Alam
ff83fa6be8 Update main.jsx 2022-03-19 00:13:04 +06:00
Ariful Alam
100bd7816e Add react-helmet-async 2022-03-19 00:11:24 +06:00
Ariful Alam
cf043dab4c Add contexts 2022-03-19 00:10:13 +06:00
Ariful Alam
6979ee8c5c Add utils.js 2022-03-19 00:08:38 +06:00
Ariful Alam
6cb49fc61b Add colors.json 2022-03-19 00:06:11 +06:00
Ariful Alam
91667f155f Update index.html 2022-03-18 23:58:39 +06:00
Ariful Alam
50f0f49afe Add assets in public dir 2022-03-18 23:55:36 +06:00
Ariful Alam
54b21cfcbc Update metadata 2022-03-18 23:53:21 +06:00
Ariful Alam
18e7e671f3 Update LICENSE 2022-03-18 23:49:57 +06:00
Ariful Alam
1ff81c8358 Add daisyui 2022-03-18 23:48:35 +06:00
Ariful Alam
f01ca017a3 Add Tailwind CSS (v3) 2022-03-18 23:46:57 +06:00
Ariful Alam
528c278890 Initialize with vite.js 2022-03-18 23:42:31 +06:00
Ariful Alam
9c324acddf Update deploy action file 2022-03-18 23:33:18 +06:00
Ariful Alam
7b7502a118 Remove folders and files 2022-03-18 23:30:10 +06:00
Ariful Alam
6a9496aa51 Update config.js 2022-03-18 11:57:01 +06:00
Ariful Alam
0bc0d0aa9c Update README.md 2022-03-08 20:22:39 +06:00
Ariful Alam
59e53ac245 Update README.md 2022-01-05 20:51:14 +06:00
71 changed files with 11098 additions and 41817 deletions

24
.eslintignore Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

29
.eslintrc.js Normal file
View File

@@ -0,0 +1,29 @@
// .eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:prettier/recommended',
],
settings: {
react: {
version: 'detect',
},
},
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 'latest',
sourceType: 'module',
},
plugins: ['react'],
rules: {
'react/react-in-jsx-scope': 'off',
},
};

View File

@@ -1,39 +0,0 @@
name: Build and Publish to gh-pages Branch
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Set up Node
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Cache dependencies
uses: actions/cache@v2
with:
path: |
**/node_modules
key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
run: npm install
- name: Build
run: npm run build
env:
CI: ""
- name: Deploy
uses: JamesIves/github-pages-deploy-action@4.1.4
with:
branch: gh-pages
folder: build

41
.github/workflows/deploy.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: Deploy to gh-pages Branch
on:
push:
branches: [main]
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up Node
uses: actions/setup-node@v3
with:
node-version: 16.x
- name: Cache dependencies
uses: actions/cache@v2
with:
path: |
**/node_modules
key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
env:
CI: ''
- name: Deploy
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
folder: dist

38
.github/workflows/test-deploy.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: Test Deployment
on:
pull_request:
branches: [main]
jobs:
test-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up Node
uses: actions/setup-node@v3
with:
node-version: 16.x
- name: Cache dependencies
uses: actions/cache@v2
with:
path: |
**/node_modules
key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
run: npm ci
- name: Run lint
run: npm run lint
- name: Run prettier
run: npm run prettier
- name: Build
run: npm run build
env:
CI: ''

41
.gitignore vendored
View File

@@ -1,23 +1,24 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

14
.npmignore Normal file
View File

@@ -0,0 +1,14 @@
public
src
.eslintignore
.eslintrc.js
.prettierignore
.prettierrc
CODE_OF_CONDUCT.md
CONTRIBUTING.md
gitprofile.config.js
index.html
library.config.js
tailwind.config.js
vite.config.js
stats.html

24
.prettierignore Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

8
.prettierrc Normal file
View File

@@ -0,0 +1,8 @@
{
"semi": true,
"arrowParens": "always",
"bracketSpacing": true,
"printWidth": 80,
"singleQuote": true,
"tabWidth": 2
}

76
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or
advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at fdkhadra@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

44
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,44 @@
# Contributing
👍🎉 First off, thanks for taking the time to contribute! 🎉👍
If you have found an issue or would like to request a new feature, simply create a new issue detailing the request. We also welcome pull requests. See below for information on getting started with development and submitting pull requests.
Please note we have a [code of conduct](https://github.com/arifszn/gitprofile/blob/main/CODE_OF_CONDUCT.md), please follow it in all your interactions with the project.
## Found an Issue?
If you find a bug in the source code or a mistake in the documentation, you can help us by
submitting an issue to our [GitHub Repository](https://github.com/arifszn/gitprofile/issues/new). Even better you can submit a Pull Request
with a fix.
## Submitting a Pull Request
- If applicable, update the `readme`
- Use `npm run lint` and `npm run prettier` before committing
- Example for a commit message
```
Fix type validation for typescript
```
### Developing
Fork, then clone the repo:
```sh
git clone https://github.com/your-username/gitprofile.git
cd gitprofile
```
Install dependencies:
```sh
npm install
```
Run dev server:
```sh
npm run dev
```

View File

@@ -198,4 +198,4 @@
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
limitations under the License.

418
README.md
View File

@@ -1,98 +1,153 @@
<h1 align="center">GitProfile</h1>
<p align="center">Easy to use automatic portfolio builder for every GitHub user!</p>
<p align="center">
<a href="http://arifszn.github.io/ezprofile" target="_blank">
<img src="https://arifszn.github.io/assets/img/hosted/ezprofile/logo.png" alt="ezProfile" title="ezProfile" width="80">
<a href="https://github.com/arifszn/gitprofile/blob/main/CONTRIBUTING.md">
<img src="https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat"/>
</a>
<a href="https://github.com/arifszn/gitprofile/blob/main/LICENSE">
<img src="https://img.shields.io/github/license/arifszn/gitprofile"/>
</a>
<a href="https://twitter.com/intent/tweet?text=Check%20out%20the%20portfolio%20builder.%20Create%20an%20automatic%20portfolio%20based%20on%20GitHub%20profile.&url=https://github.com/arifszn/gitprofile&hashtags=javascript,opensource,js,webdev,developers">
<img src="https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Fgithub.com%2Farifszn%2Fgitprofile"/>
</a>
</p>
<h1 align="center">ezProfile</h1>
<p align="center">A modern, responsive and customizable portfolio builder for Developers!</p>
<p align="center">https://arifszn.github.io/ezprofile</p>
<p align="center">
<a href="https://arifszn.github.io/gitprofile">
<img src="https://arifszn.github.io/assets/img/hosted/gitprofile/preview.gif" width="60%" alt="Preview"/>
</a>
<br/>
<a href="#arifszn"><img src="https://arifszn.github.io/assets/img/drop-shadow.png" width="60%" alt="Shadow"/></a>
</p>
What if you could create your portfolio in 5 minutes just by providing your GitHub username and even host it without any cost? Do you want to display your skills, job history, education, or blog posts on your portfolio? Introducing **GitProfile**.
**GitProfile** is an easy to use portfolio builder where you can create a portfolio page automatically by just providing your GitHub username. It is built using React.js on top of Vite.js. But it's not necessary to have knowledge on these to get you started. You can make your own copy with zero coding experience.
**Features:**
✓ [Easy to Setup](#-installation--setup)
✓ [30 Themes](#themes)
✓ [Google Analytics](#google-analytics)
✓ [Hotjar](#hotjar)
✓ [SEO](#seo)
✓ [Avatar and Bio](#avatar-and-bio)
✓ [Social Links](#social-links)
✓ [Skills](#skills)
✓ [Experience](#experience)
✓ [Education](#education)
✓ [Projects](#projects)
✓ [Blog Posts](#blog-posts)
To view a live example, **[click here](https://arifszn.github.io/gitprofile)**.
Or try it **[online](https://stackblitz.com/edit/gitprofile)**.
## 🛠 Installation & Setup
There are two ways to use **GitProfile**. Use either one.
<details>
<summary>Forking this repo (Click to expand)</summary>
<br/>
<p align="center">
<a href="https://arifszn.github.io/ezprofile">
<img src="https://arifszn.github.io/assets/img/hosted/ezprofile/preview.gif" width="60%" alt="Preview"/>
</a>
<br/>
<a href="#arifszn"><img src="https://arifszn.github.io/assets/img/drop-shadow.png" width="60%" alt="Shadow"/></a>
</p>
These instructions will get you a copy of the project and deploy your portfolio online!
**ezProfile** is an easy-to-customize personal dev portfolio builder that is created with React.js. When you manage the code in a GitHub repository, it will automatically render a webpage with the owner's profile information, including a photo, bio, and public repositories. Also, it includes space to highlight your details, job history, education history, skills, and recent blog posts.
- **Fork repo:** Click [here](https://github.com/arifszn/gitprofile/fork) to fork the repo so you have your own project to customize. A "fork" is a copy of a repository.
- **Rename repo:** Rename your forked repository to `username.github.io` in GitHub, where `username` is your GitHub username (or organization name).
- **Enable workflows:** Go to your repo's **Actions** page and enable workflows.
It's all possible using [GitHub API](https://developer.github.com/v3/) (for automatically populating your website with content) and [Article-api](https://github.com/arifszn/article-api) (for fetching recent blog posts).
![Workflows](https://arifszn.github.io/assets/img/hosted/gitprofile/workflows.png)
✓ [21 Themes](#themes)\
✓ [Google Analytics](#google-analytics)\
✓ [Hotjar](#hotjar)\
✓ [Meta Tags](#meta-tags)\
✓ [Avatar and Bio](#avatar-and-bio)\
✓ [Social Links](#social-links)\
✓ [Skills](#skills)\
✓ [Experience](#experience)\
✓ [Education](#education)\
✓ [Projects](#projects)\
✓ [Blog Posts](#blog-posts)
- **Base Value:** Open `vite.config.js`, and change `base`'s value.
To view a live example, **[click here](https://arifszn.github.io/ezprofile)**.
- If you are deploying to `https://<USERNAME>.github.io/`, set `base` to `'/'`.
- If you are deploying to `https://<USERNAME>.github.io/<REPO>/`, for example your repository is at `https://github.com/<USERNAME>/<REPO>`, then set `base` to `'/<REPO>/'`.
```js
// vite.config.js
{
base: '/',
// ...
}
```
## 🛠 Installation & Set Up
- **First Commit:** Now commit to your **main** branch with your changes. The CI/CD pipeline will publish your page at the `gh-pages` branch automatically.
- **Change deploy branch:** Go to your repo's **Settings** ➜ **Pages** ➜ **Source** and change the branch to `gh-pages` and click **save**.
These instructions will get you a copy of the project and deploy your website online!
Your personal portfolio will be live at `username.github.io`. Any time you commit a change to the **main** branch, the website will be automatically updated.
- **[Fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo)** the repo so you have your own project to customize by clicking the fork icon on the top right side. A "fork" is a copy of a repository.
- Rename your forked repository to <code>username.github.io</code> in github, where <code>username</code> is your GitHub username (or organization name).
- Go to your repo's **Actions** page and enable workflows.
![Workflows](https://arifszn.github.io/assets/img/hosted/ezprofile/workflows.png)
If you see only `README` at `username.github.io`, be sure to change your GitHub Page's source to `gh-pages` branch. See [how to](https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site). Also, if you face any issue rendering the website, double-check the `base` value in the `vite.config.js`.
- Open <code>package.json</code>, and change <code>homepage</code>'s value to <code>https://username.github.io</code>.
```js
// package.json
{
// ...
"homepage": "https://username.github.io",
}
```
As this is a vite project, you can also host your website to Netlify, Vercel, Heroku, or other popular services. Please refer to this [doc](https://vitejs.dev/guide/static-deploy.html) for a detailed deployment guide to other services.
- Now commit to your **main** branch with your changes.
- The CI/CD pipeline will publish your page at the gh-pages branch automatically.
- Go to your repo's **Settings** -> **Pages** -> **Source** and change the branch to gh-pages and click **save**.
- Your personal portfolio will be live at <code>username.github.io</code>.
- Any time you commit a change to the **main** branch, the website will be automatically updated.
</details>
You can skip the above steps and do a manual deployment by running <code>npm run deploy</code>. For more info, visit [here](https://create-react-app.dev/docs/deployment/#github-pages).
Or
As this is a create react app, you can also host your website to Netlify, Vercel, Heroku, or other popular services. Please refer to this [doc](https://create-react-app.dev/docs/deployment) for a detailed deployment guide to other services.
<details>
<summary>Installing as package (Click to expand)</summary>
If you see only <code>README</code> at <code>username.github.io</code>, be sure to change your GitHub Page's source to <code>gh-pages</code> branch. See [how to](https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site). Also, if you face any issue rendering the website, double-check the `homepage` value in the package.json.
<br/>
First Install **GitProfile** via <a href="https://www.npmjs.com/package/@arifszn/gitprofile">NPM</a>.
```sh
npm install @arifszn/gitprofile
```
Or via <a href="https://yarnpkg.com/package/@arifszn/gitprofile">Yarn</a>.
```sh
yarn add @arifszn/gitprofile
```
Then, import the package, import and style and provide the config.
```js
import GitProfile from '@arifszn/gitprofile';
import '@arifszn/gitprofile/dist/style.css';
function App() {
return (
<GitProfile
config={{
github: {
username: 'arifszn',
},
}}
/>
);
}
export default App;
```
List of all config [here](#-customization).
</details>
**If you face any problems or have any questions, open an issue [here](https://github.com/arifszn/gitprofile/issues).**
## 🎨 Customization
All the magic happens in the file <code>src/config.js</code>. Open it and modify it according to your preference.
These are the default values:
<details>
<summary>config.js</summary>
All the magic happens in the file `gitprofile.config.js`. Open it and modify it according to your preference.
```js
// config.js
module.exports = {
// gitprofile.config.js
const config = {
github: {
username: 'arifszn', // Your GitHub org/user name. (Required)
sortBy: 'stars', // stars | updated
limit: 8, // How many projects to display.
exclude: {
forks: false, // Forked projects will not be displayed if set to true.
projects: [] // These projects will not be displayed. example: ['my-project1', 'my-project2']
}
projects: [], // These projects will not be displayed. example: ['my-project1', 'my-project2']
},
},
social: {
linkedin: '',
@@ -101,66 +156,63 @@ module.exports = {
dribbble: '',
behance: '',
medium: '',
devto: '',
dev: '',
website: '',
phone: '',
email: ''
email: '',
},
skills: [
'JavaScript',
'React.js',
],
skills: ['JavaScript', 'React.js'],
experiences: [
{
company: 'Company name 1',
position: 'Software Engineer',
from: 'July 2019',
to: 'Present'
to: 'Present',
},
{
company: 'Company name 2',
position: 'Jr. Software Engineer',
from: 'January 2019',
to: ' June 2019'
}
to: ' June 2019',
},
],
education: [
{
institution: 'Institution name 1',
degree: 'Bachelor of Science',
degree: 'ABC',
from: '2015',
to: '2019'
to: '2019',
},
{
institution: 'Institution name 2',
degree: 'Higher Secondary Certificate (HSC)',
degree: 'XYZ',
from: '2012',
to: '2014',
}
},
],
// Display blog posts from your medium or dev account. (Optional)
blog: {
// Display blog posts from your medium or dev.to account. (Optional)
source: 'dev.to', // medium | dev.to
source: 'dev', // medium | dev
username: 'arifszn',
limit: 5 // How many posts to display. Max is 10.
limit: 5, // How many posts to display. Max is 10.
},
googleAnalytics: {
// GA3 tracking id/GA4 tag id
id: '' // UA-XXXXXXXXX-X | G-XXXXXXXXXX
id: '', // UA-XXXXXXXXX-X | G-XXXXXXXXXX
},
hotjar: {
id: '',
snippetVersion : 6
snippetVersion: 6,
},
themeConfig: {
default: 'light',
defaultTheme: 'light',
// Hides the theme change switch
// Useful if you want to support a single color mode
disableSwitch: false,
// Should we use the prefers-color-scheme media-query,
// using user system preferences, instead of the hardcoded default
// Should use the prefers-color-scheme media-query,
// using user system preferences, instead of the hardcoded defaultTheme
respectPrefersColorScheme: true,
// Available themes. To remove any theme, exclude from here.
@@ -185,97 +237,123 @@ module.exports = {
'wireframe',
'black',
'luxury',
'dracula'
]
}
}
```
</details>
'dracula',
'cmyk',
'autumn',
'business',
'acid',
'lemonade',
'night',
'coffee',
'winter',
'procyon',
],
// Custom theme
customTheme: {
primary: '#fc055b',
secondary: '#219aaf',
accent: '#e8d03a',
neutral: '#2A2730',
'base-100': '#E3E3ED',
'--rounded-box': '3rem',
'--rounded-btn': '3rem',
},
},
};
```
### Themes
There are 21 themes available that can be selected from the dropdown.
There are 30 themes available that can be selected from the dropdown.
The default theme can be specified.
```js
// config.js
// gitprofile.config.js
module.exports = {
// ...
themeConfig: {
default: 'light',
defaultTheme: 'light',
// ...
}
}
},
};
```
![Theme Dropdown](https://arifszn.github.io/assets/img/hosted/ezprofile/themes-1.png)
<p align="center">
<img src="https://arifszn.github.io/assets/img/hosted/gitprofile/theme-dropdown.png" alt="Theme Dropdown" width="50%">
</p>
Here are some screenshots of different themes.\
<br/>
![Themes](https://arifszn.github.io/assets/img/hosted/ezprofile/themes-2.png)\
<br/>
![Themes](https://arifszn.github.io/assets/img/hosted/ezprofile/themes-3.png)\
<br/>
![Themes](https://arifszn.github.io/assets/img/hosted/ezprofile/themes-4.png)\
<br/>
![Themes](https://arifszn.github.io/assets/img/hosted/ezprofile/themes-5.png)\
<br/>
![Themes](https://arifszn.github.io/assets/img/hosted/ezprofile/themes-6.png)\
<br/>
![Themes](https://arifszn.github.io/assets/img/hosted/ezprofile/themes-7.png)
You can create your own custom theme by modifying these values. Theme `procyon` will have the custom styles.
```js
// gitprofile.config.js
module.exports = {
// ...
themeConfig: {
customTheme: {
primary: '#fc055b',
secondary: '#219aaf',
accent: '#e8d03a',
neutral: '#2A2730',
'base-100': '#E3E3ED',
'--rounded-box': '3rem',
'--rounded-btn': '3rem',
},
// ...
},
};
```
<p align="center">
<img src="https://arifszn.github.io/assets/img/hosted/gitprofile/themes.png" alt="Themes">
</p>
### Google Analytics
ezFolio supports both GA3 and GA4. If you do not want to use Google Analytics, keep the <code>id</code> empty.
**GitProfile** supports both GA3 and GA4. If you do not want to use Google Analytics, keep the `id` empty.
```js
// config.js
// gitprofile.config.js
module.exports = {
// ...
googleAnalytics: {
id: ''
id: '',
},
}
};
```
Besides tracking visitors, ezFolio will track click events on projects and blog posts, and send them to Google Analytics.\
<br/>
![Event](https://www.arifszn.com/assets/img/hosted/ezprofile/event.png)
Besides tracking visitors, it will track `click events` on projects and blog posts, and send them to Google Analytics.
### Hotjar
ezProfile supports hotjar. If you do not want to use Hotjar, keep the <code>id</code> empty.
**GitProfile** supports hotjar. If you do not want to use Hotjar, keep the `id` empty.
```js
// config.js
// gitprofile.config.js
module.exports = {
// ...
hotjar: {
id: '',
snippetVersion : 6
snippetVersion: 6,
},
}
};
```
### Meta Tags
Meta tags will be auto-generated from configs dynamically. However, you can also manually add meta tags in <code>public\index.html</code>
### SEO
Meta tags will be auto-generated from configs dynamically. However, you can also manually add meta tags in `public/index.html`.
### Avatar and Bio
Your github avatar and bio will be displayed here.\
<br/>
![Avatar Bio](https://arifszn.github.io/assets/img/hosted/ezprofile/avatar-card.png)
Your avatar and bio will be fetched from GitHub automatically.
### Social Links
ezProfile supports linking your social media services you're using, including LinkedIn, Twitter, Facebook, Dribbble, Behance, Medium, dev.to, personal website, phone and email.
You can link your social media services you're using, including LinkedIn, Twitter, Facebook, Dribbble, Behance, Medium, dev, personal website, phone and email.
```js
// config.js
// gitprofile.config.js
module.exports = {
// ...
social: {
@@ -285,37 +363,34 @@ module.exports = {
dribbble: '',
behance: '',
medium: '',
devto: '',
dev: '',
website: 'https://arifszn.github.io',
phone: '',
email: ''
email: '',
},
}
};
```
### Skills
To showcase your skills provide them here.
```js
// config.js
// gitprofile.config.js
module.exports = {
// ...
skills: [
'JavaScript',
'React.js',
],
}
skills: ['JavaScript', 'React.js'],
};
```
Empty array will hide the skills section.
### Experience
Provide your job history in <code>experiences</code>.
Provide your job history in `experiences`.
```js
// config.js
// gitprofile.config.js
module.exports = {
// ...
experiences: [
@@ -323,26 +398,26 @@ module.exports = {
company: 'Company name 1',
position: 'Software Engineer',
from: 'July 2019',
to: 'Present'
to: 'Present',
},
{
company: 'Company name 2',
position: 'Jr. Software Engineer',
from: 'January 2019',
to: ' June 2019'
}
to: ' June 2019',
},
],
}
};
```
Empty array will hide the experience section.
### Education
Provide your education history in <code>education</code>.
Provide your education history in `education`.
```js
// config.js
// gitprofile.config.js
module.exports = {
// ...
education: [
@@ -350,27 +425,26 @@ module.exports = {
institution: 'Institution name 1',
degree: 'Bachelor of Science',
from: '2015',
to: '2019'
to: '2019',
},
{
institution: 'Institution name 2',
degree: 'Higher Secondary Certificate (HSC)',
from: '2012',
to: '2014',
}
},
],
}
};
```
Empty array will hide the education section.
### Projects
Your public repo from github will be displayed here automatically. You can limit how many projects do you want to be displayed. Also, you can hide forked or specific repo.
Your public repo from GitHub will be displayed here automatically. You can limit how many projects do you want to be displayed. Also, you can hide forked or specific repo.
```js
// config.js
// gitprofile.config.js
module.exports = {
// ...
github: {
@@ -379,48 +453,42 @@ module.exports = {
limit: 8,
exclude: {
forks: false,
projects: ['my-project1', 'my-project2']
}
projects: ['my-project1', 'my-project2'],
},
},
}
};
```
### Blog Posts
If you have [medium](https://medium.com) or [dev.to](https://dev.to) account, you can show your recent blog posts in here just by providing your medium/dev.to username. You can limit how many posts to display (Max is <code>10</code>).
If you have [medium](https://medium.com) or [dev](https://dev.to) account, you can show your recent blog posts in here just by providing your medium/dev username. You can limit how many posts to display (Max is `10`).
```js
// config.js
// gitprofile.config.js
module.exports = {
// ...
blog: {
source: 'dev.to',
source: 'dev',
username: 'arifszn',
limit: 5
limit: 5,
},
}
};
```
![Blog](https://arifszn.github.io/assets/img/hosted/ezprofile/blog.png)
The posts are fetched by [Article-api](https://github.com/arifszn/article-api).
## 📢 Please Read
I intend to keep my works open source. Please do not discourage me by claiming this work by copying it as your own. However, You are open to use this project by forking it and change any code necessary by giving attribute to the original author. Please see this [issue](https://github.com/arifszn/ezprofile/issues/11) for more info.
![Blog](https://arifszn.github.io/assets/img/hosted/gitprofile/blog.png)
The posts are fetched by [Blog-js](https://github.com/arifszn/blog-js).
## 💖 Support
If you are using this project and happy with it or just want to encourage me to continue creating stuff, you can do it by just starring and sharing the project.
<a href="https://www.buymeacoffee.com/arifszn" target="_blank">
<img src="https://raw.githubusercontent.com/arifszn/arifszn/main/assets/bmc-button.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" >
</a>
## 💡 Contribute
## 💡 Contributing
Any contributors who want to make this project better can make contributions, which will be greatly appreciated. To contribute, clone this repo locally and commit your code to a new branch. Feel free to create an issue or make a pull request.
To contribute, see the [Contributing guide](https://github.com/arifszn/gitprofile/blob/main/CONTRIBUTING.md).
## 📄 License
**ezProfile** is licensed under the [Apache-2.0 License](https://github.com/arifszn/ezprofile/blob/main/LICENSE).
**GitProfile** is licensed under the [Apache-2.0 License](https://github.com/arifszn/gitprofile/blob/main/LICENSE).

View File

@@ -1,10 +0,0 @@
module.exports = {
style: {
postcss: {
plugins: [
require('tailwindcss'),
require('autoprefixer'),
],
},
},
}

145
gitprofile.config.js Normal file
View File

@@ -0,0 +1,145 @@
// gitprofile.config.js
const config = {
github: {
username: 'arifszn', // Your GitHub org/user name. (Required)
sortBy: 'stars', // stars | updated
limit: 10, // How many projects to display.
exclude: {
forks: false, // Forked projects will not be displayed if set to true.
projects: ['laravel-ecommerce'], // These projects will not be displayed. example: ['my-project1', 'my-project2']
},
},
social: {
linkedin: 'ariful-alam',
twitter: 'arif_szn',
facebook: '',
dribbble: '',
behance: '',
medium: '',
dev: 'arifszn',
website: 'https://arifszn.github.io',
phone: '',
email: 'arifulalamszn@gmail.com',
},
skills: [
'PHP',
'Laravel',
'JavaScript',
'React.js',
'Node.js',
'MySQL',
'Git',
'Docker',
'CSS',
'Antd',
'Tailwind',
'Bootstrap',
],
experiences: [
{
company: 'Monstarlab Bangladesh',
position: 'Backend Engineer II',
from: 'September 2021',
to: 'Present',
},
{
company: 'My Offer 360 Degree',
position: 'Web Application Developer',
from: 'July 2019',
to: 'August 2021',
},
],
education: [
{
institution: 'American International University-Bangladesh',
degree: 'Bachelor of Science',
from: '2015',
to: '2019',
},
{
institution: 'Cantonment College, Jessore',
degree: 'Higher Secondary Certificate (HSC)',
from: '2012',
to: '2014',
},
{
institution: 'Chowgacha Shahadat Pilot High School',
degree: 'Secondary School Certificate (SSC)',
from: '2007',
to: '2012',
},
],
// Display blog posts from your medium or dev account. (Optional)
blog: {
source: 'dev', // medium | dev
username: 'arifszn',
limit: 3, // How many posts to display. Max is 10.
},
googleAnalytics: {
// GA3 tracking id/GA4 tag id UA-XXXXXXXXX-X | G-XXXXXXXXXX
id: 'G-WLLB5E14M6', // Please remove this and use your own tag id or keep it empty
},
hotjar: {
id: '2617601', // Please remove this and use your own id or keep it empty
snippetVersion: 6,
},
themeConfig: {
defaultTheme: 'corporate',
// Hides the switch in the navbar
// Useful if you want to support a single color mode
disableSwitch: false,
// Should use the prefers-color-scheme media-query,
// using user system preferences, instead of the hardcoded defaultTheme
respectPrefersColorScheme: false,
// Available themes. To remove any theme, exclude from here.
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',
],
// Custom theme
customTheme: {
primary: '#fc055b',
secondary: '#219aaf',
accent: '#e8d03a',
neutral: '#2A2730',
'base-100': '#E3E3ED',
'--rounded-box': '3rem',
'--rounded-btn': '3rem',
},
},
};
export default config;

15
index.html Normal file
View File

@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="manifest" href="/manifest.json" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Portfolio</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

31
library.config.js Normal file
View File

@@ -0,0 +1,31 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwind from 'tailwindcss';
import autoprefixer from 'autoprefixer';
import tailwindConfig from './tailwind.config.js';
import path from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
css: {
postcss: {
plugins: [tailwind(tailwindConfig), autoprefixer],
},
},
build: {
lib: {
entry: path.resolve(__dirname, 'src/components/GitProfile.jsx'),
name: 'GitProfile',
fileName: (format) => `gitprofile.${format}.js`,
},
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'React',
},
},
},
},
});

44951
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,79 +1,90 @@
{
"name": "ezprofile",
"version": "1.1.1",
"description": "Kickstart your personal portfolio with Github Api and blog",
"homepage": "https://arifszn.github.io/ezprofile",
"private": true,
"license": "Apache-2.0",
"author": "arifszn",
"repository": {
"type": "git",
"url": "https://github.com/arifszn/ezprofile.git"
"name": "@arifszn/gitprofile",
"description": "Create an automatic portfolio based on GitHub profile",
"version": "2.0.5",
"license": "Apache-2.0",
"author": "arifszn",
"repository": {
"type": "git",
"url": "https://github.com/arifszn/gitprofile.git"
},
"bugs": {
"url": "https://github.com/arifszn/gitprofile/issues"
},
"files": [
"dist/gitprofile.es.js",
"dist/gitprofile.umd.js",
"dist/style.css",
"types"
],
"main": "./dist/gitprofile.umd.js",
"module": "./dist/gitprofile.es.js",
"exports": {
".": {
"import": "./dist/gitprofile.es.js",
"require": "./dist/gitprofile.umd.js"
},
"dependencies": {
"@craco/craco": "^6.2.0",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"article-api": "^1.0.5",
"axios": "^0.23.0",
"daisyui": "^1.12.1",
"gh-pages": "^3.2.3",
"moment": "^2.29.1",
"prop-types": "^15.7.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-helmet-async": "^1.1.0",
"react-hotjar": "^3.0.1",
"react-icons": "^4.2.0",
"react-scripts": "4.0.3",
"sass": "^1.38.0",
"web-vitals": "^1.0.1"
},
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "react-scripts eject",
"predeploy": "npm run build",
"deploy": "gh-pages -d build"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"autoprefixer": "^9.8.6",
"postcss": "^7.0.36",
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.7"
},
"keywords": [
"personal-site",
"template",
"portfolio",
"personal-website",
"portfolio-website",
"portfolio-site",
"portfolio-template",
"portfolio-page",
"developer-portfolio",
"portfolio-project",
"github-portfolio",
"react-portfolio",
"github-api"
]
"./dist/style.css": "./dist/style.css"
},
"typings": "./types/index.d.ts",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"build:library": "vite build --config library.config.js",
"lint": "eslint --ext .js,.jsx .",
"lint:fix": "eslint --ext .js,.jsx --fix .",
"prettier": "prettier --check './**/*.{js,jsx,ts,tsx,css,md,json}'",
"prettier:fix": "prettier --write './**/*.{js,jsx,ts,tsx,css,md,json}'"
},
"dependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"@arifszn/blog-js": "^2.0.0",
"@vitejs/plugin-react": "^1.0.7",
"autoprefixer": "^10.4.4",
"axios": "^0.26.1",
"daisyui": "^2.11.0",
"eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.29.4",
"moment": "^2.29.1",
"postcss": "^8.4.12",
"prettier": "^2.6.0",
"prop-types": "^15.8.1",
"react-helmet-async": "^1.2.3",
"react-hotjar": "^5.0.0",
"react-icons": "^4.3.1",
"tailwindcss": "^3.0.23",
"vite": "^2.8.0"
},
"keywords": [
"git-profile",
"gitprofile",
"gitportfolio",
"personal-site",
"template",
"portfolio",
"resume",
"cv",
"personal-website",
"portfolio-website",
"portfolio-site",
"portfolio-template",
"portfolio-page",
"developer-portfolio",
"portfolio-project",
"github-portfolio",
"tailwind-portfolio",
"vite-portfolio",
"projects",
"open-source",
"git",
"react-portfolio",
"github",
"github-api"
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 434 B

After

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 767 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,18 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Portfolio</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -1,5 +1,5 @@
{
"short_name": "ezProfile",
"short_name": "GitProfile",
"name": "Personal Portfolio",
"icons": [
{

View File

@@ -1,178 +0,0 @@
import axios from "axios";
import { Fragment, useCallback, useContext, useEffect, useState } from "react";
import AvatarCard from "./components/AvatarCard";
import ErrorPage from "./components/ErrorPage";
import ThemeChanger from "./components/ThemeChanger";
import config from "./config";
import moment from 'moment';
import Details from "./components/Details";
import Skill from "./components/Skill";
import Experience from "./components/Experience";
import Education from "./components/Education";
import Project from "./components/Project";
import Blog from "./components/Blog";
import MetaTags from "./components/MetaTags";
import { LoadingContext } from "./contexts/LoadingContext";
import { ThemeContext } from "./contexts/ThemeContext";
function App() {
const [theme] = useContext(ThemeContext);
const [, setLoading] = useContext(LoadingContext);
const [profile, setProfile] = useState(null);
const [repo, setRepo] = useState(null);
const [error, setError] = useState(null);
const [rateLimit, setRateLimit] = useState(null);
useEffect(() => {
if (theme) {
document.documentElement.setAttribute('data-theme', theme);
}
}, [theme])
const loadData = useCallback(() => {
axios.get(`https://api.github.com/users/${config.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);
})
.then(() => {
let excludeRepo = ``;
config.github.exclude.projects.forEach(project => {
excludeRepo += `+-repo:${config.github.username}/${project}`;
});
let query = `user:${config.github.username}+fork:${!config.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`;
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])
useEffect(() => {
loadData();
}, [loadData])
const handleError = (error) => {
console.error('Error:', error);
try {
setRateLimit({
remaining: error.response.headers['x-ratelimit-remaining'],
reset: moment(new Date(error.response.headers['x-ratelimit-reset'] * 1000)).fromNow(),
});
if (error.response.status === 403) {
setError(429);
} else if (error.response.status === 404) {
setError(404);
} else {
setError(500);
}
} catch (error2) {
setError(500);
}
}
return (
<Fragment>
<MetaTags profile={profile}/>
<div className="fade-in h-screen">
{
error ? (
<ErrorPage
status={`${error}`}
title={(error === 404) ? 'The Github Username is Incorrect' : (
error === 429 ? 'Too Many Requests.' : `Ops!!`
)}
subTitle={
(error === 404) ? (
<p>
Please provide correct github username in <code>src\config.js</code>
</p>
) : (
error === 429 ? (
<p>
Oh no, you hit the{' '}
<a
href="https://developer.github.com/v3/rate_limit/"
target="_blank"
rel="noopener noreferrer"
>
rate limit
</a>
! Try again later{rateLimit && ` ${rateLimit.reset}`}.
</p>
) : `Something went wrong`
)
}
/>
) : (
<Fragment>
<div className="p-4 lg:p-10 min-h-full bg-base-200">
<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">
{
!config.themeConfig.disableSwitch && (
<ThemeChanger/>
)
}
<AvatarCard profile={profile}/>
<Details profile={profile}/>
<Skill/>
<Experience/>
<Education/>
</div>
</div>
<div className="lg:col-span-2 col-span-1">
<div className="grid grid-cols-1 gap-6">
<Project repo={repo}/>
<Blog/>
</div>
</div>
</div>
</div>
{/* DO NOT REMOVE/MODIFY THE FOOTER. FOR MORE INFO https://github.com/arifszn/ezprofile#-please-read */}
<footer className="p-4 footer bg-base-200 text-base-content footer-center">
<div>
<p className="font-mono text-sm">Made with <a className="text-primary" href="https://github.com/arifszn/ezprofile" target="_blank" rel="noreferrer">ezProfile</a> and </p>
</div>
</footer>
</Fragment>
)
}
</div>
</Fragment>
);
}
export default App;

8
src/App.jsx Normal file
View File

@@ -0,0 +1,8 @@
import config from '../gitprofile.config';
import GitProfile from './components/GitProfile';
function App() {
return <GitProfile config={config} />;
}
export default App;

View File

@@ -1,8 +0,0 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

92
src/assets/index.css Normal file
View File

@@ -0,0 +1,92 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
* {
scrollbar-width: thin;
}
::-webkit-scrollbar-track {
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
-moz-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
}
@media screen and (min-width: 966px) {
::-webkit-scrollbar,
.scroller {
width: 8px;
height: 8px;
background-color: #f1f1f1;
}
}
::-webkit-scrollbar-thumb {
background-color: #888;
border-radius: 10px;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
.text-base-content-important {
color: hsla(var(--bc) / var(--tw-text-opacity)) !important;
}
svg {
vertical-align: unset;
}
.z-hover {
transition: all ease-in-out 0.3s !important;
}
.z-hover:hover,
.z-hover:focus,
.z-hover:active {
transition: transform 0.3s !important;
-ms-transform: scale(1.01) !important;
-webkit-transform: scale(1.01) !important;
transform: scale(1.01) !important;
}
.pb-0-important {
padding-bottom: 0 !important;
}
.fade-in {
opacity: 1;
animation-name: fadeIn;
animation-iteration-count: 1;
animation-timing-function: ease-in;
animation-duration: 1s;
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@-webkit-keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

View File

@@ -1,71 +0,0 @@
import { fallbackImage, skeleton } from "../helpers/utils";
import LazyImage from "./LazyImage";
import PropTypes from 'prop-types';
import { useContext } from "react";
import { LoadingContext } from "../contexts/LoadingContext";
const AvatarCard = (props) => {
const [loading] = useContext(LoadingContext);
return (
<div className="card shadow-lg compact bg-base-100">
<div className="grid place-items-center py-8">
{
(loading || !props.profile) ? (
<div className="avatar opacity-90">
<div className="mb-8 rounded-full w-32 h-32">
{
skeleton({
width: 'w-full',
height: 'h-full',
shape: '',
})
}
</div>
</div>
) : (
<div className="avatar opacity-90">
<div className="mb-8 rounded-full w-32 h-32 ring ring-primary ring-offset-base-100 ring-offset-2">
{
<LazyImage
src={props.profile.avatar ? props.profile.avatar : fallbackImage}
alt={props.profile.name}
placeholder={
skeleton({
width: 'w-full',
height: 'h-full',
shape: '',
})
}
/>
}
</div>
</div>
)
}
<div className="text-center mx-auto px-8">
<h5 className="font-bold text-2xl">
{
(loading || !props.profile) ? (
skeleton({ width: 'w-48', height: 'h-8' })
) : <span className="opacity-70">{props.profile.name}</span>
}
</h5>
<div className="mt-3 text-base-content text-opacity-60">
{
(loading || !props.profile) ? (
skeleton({ width: 'w-48', height: 'h-5' })
) : props.profile.bio
}
</div>
</div>
</div>
</div>
)
}
AvatarCard.propTypes = {
profile: PropTypes.object
}
export default AvatarCard;

View File

@@ -1,197 +0,0 @@
import { getDevtoArticle, getMediumArticle } from "article-api";
import moment from "moment";
import { Fragment, useContext, useEffect, useState } from "react";
import { CgHashtag } from 'react-icons/cg';
import config from "../config";
import { LoadingContext } from "../contexts/LoadingContext";
import { ga, skeleton } from "../helpers/utils";
import LazyImage from "./LazyImage";
const displaySection = () => {
if (
typeof config.blog !== 'undefined' &&
typeof config.blog.source !== 'undefined' &&
typeof config.blog.username !== 'undefined' &&
config.blog.source &&
config.blog.username
) {
return true;
} else {
return false;
}
}
const Blog = () => {
const [articles, setArticles] = useState(null);
const [loading] = useContext(LoadingContext);
useEffect(() => {
if (displaySection()) {
if (config.blog.source === 'medium') {
getMediumArticle({
user: config.blog.username
})
.then(res => {
setArticles(res);
});
} else if (config.blog.source === 'dev.to') {
getDevtoArticle({
user: config.blog.username
})
.then(res => {
setArticles(res);
});
}
}
}, [])
const renderSkeleton = () => {
let array = [];
for (let index = 0; index < config.blog.limit; index++) {
array.push((
<div className="card shadow-lg compact bg-base-100" key={index}>
<div className="p-8 h-full w-full">
<div className="flex items-center flex-col md:flex-row">
<div className="avatar mb-5 md:mb-0">
<div className="w-24 h-24 mask mask-squircle">
{
skeleton({
width: 'w-full',
height: 'h-full',
shape: '',
})
}
</div>
</div>
<div className="w-full">
<div className="flex items-start px-4">
<div className="w-full">
<h2>
{skeleton({ width: 'w-full', height: 'h-8', className: 'mb-2 mx-auto md:mx-0' })}
</h2>
{skeleton({ width: 'w-24', height: 'h-3', className: 'mx-auto md:mx-0' })}
<div className="mt-3">
{skeleton({ width: 'w-full', height: 'h-4', className: 'mx-auto md:mx-0' })}
</div>
<div className="mt-4 flex items-center flex-wrap justify-center md:justify-start">
{skeleton({ width: 'w-32', height: 'h-4', className: "md:mr-2 mx-auto md:mx-0" })}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
))
}
return array;
}
const renderArticles = () => {
return articles && articles.slice(0, config.blog.limit).map((article, index) => (
<div
className="card shadow-lg compact bg-base-100 cursor-pointer"
key={index}
onClick={() => {
try {
if (config.googleAnalytics?.id) {
ga.event({
action: "Click Blog Post",
params: {
post: article.title
}
});
}
} catch (error) {
console.error(error);
}
window.open(article.link, '_blank')
}}
>
<div className="p-8 h-full w-full">
<div className="flex items-center flex-col md:flex-row">
<div className="avatar mb-5 md:mb-0 opacity-90">
<div className="w-24 h-24 mask mask-squircle">
<LazyImage
src={article.thumbnail}
alt={'thumbnail'}
placeholder={
skeleton({
width: 'w-full',
height: 'h-full',
shape: '',
})
}
/>
</div>
</div>
<div className="w-full">
<div className="flex items-start px-4">
<div className="text-center md:text-left w-full">
<h2 className="font-semibold text-base-content opacity-60">{article.title}</h2>
<p className="opacity-50 text-xs">
{moment(article.publishedAt).fromNow()}
</p>
<p className="mt-3 text-base-content text-opacity-60 text-sm">
{article.description}
</p>
<div className="mt-4 flex items-center flex-wrap justify-center md:justify-start">
{
article.categories.map((category, index2) => (
<div key={index2} className="flex text-sm mr-3 items-center opacity-50 font-bold font-mono">
<span><CgHashtag /></span>
<span>{category}</span>
</div>
))
}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
))
}
return (
<Fragment>
{
displaySection() && (
<div className="col-span-1 lg:col-span-2">
<div className="grid grid-cols-2 gap-6">
<div className="col-span-2">
<div className="card compact bg-base-100 shadow-sm">
<div className="card-body">
<ul className="menu row-span-3 bg-base-100 text-base-content">
<li>
<div className="pb-0-important mx-4 flex items-center justify-between">
<h5 className="card-title">
{
(loading || !articles) ? skeleton({ width: 'w-28', height: 'h-8' }) : (
<span className="opacity-70">Recent Posts</span>
)
}
</h5>
</div>
</li>
</ul>
</div>
</div>
</div>
<div className="col-span-2">
<div className="grid grid-cols-1 gap-6">
{(loading || !articles) ? renderSkeleton() : renderArticles()}
</div>
</div>
</div>
</div>
)
}
</Fragment>
)
}
export default Blog;

View File

@@ -1,307 +0,0 @@
import { MdLocationOn, MdMail } from 'react-icons/md';
import { AiFillGithub, 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 { FaBehanceSquare, FaBuilding, FaDev, FaFacebook, FaGlobe } from 'react-icons/fa';
import config from '../config';
import { skeleton } from '../helpers/utils';
import PropTypes from 'prop-types';
import { useContext } from 'react';
import { LoadingContext } from '../contexts/LoadingContext';
const Details = (props) => {
const [loading] = useContext(LoadingContext);
const renderSkeleton = () => {
let array = [];
for (let index = 0; index < 4; index++) {
array.push((
<li key={index}>
<span>
{skeleton({ width: 'w-6', height: 'h-4', className: 'mr-2' })}
{skeleton({ width: 'w-32', height: 'h-4' })}
</span>
</li>
))
}
return array;
}
return (
<div className="card shadow-lg compact bg-base-100">
<div className="card-body">
<ul className="menu row-span-3 bg-base-100 text-base-content text-opacity-60">
{
(loading || !props.profile) ? renderSkeleton() : (
<>
{
props.profile.location && (
<li>
<span>
<div>
<MdLocationOn className="mr-2"/>
</div>
<div>
{props.profile.location}
</div>
</span>
</li>
)
}
{
props.profile.company && (
<li>
<span>
<div>
<FaBuilding className="mr-2"/>
</div>
<div>
{props.profile.company}
</div>
</span>
</li>
)
}
<li>
<span>
<div>
<AiFillGithub className="mr-2"/>
</div>
<div>
<a
href={`https://github.com/${config.github.username}`}
target="_blank"
rel="noreferrer"
className="text-base-content-important"
>
{config.github.username}
</a>
</div>
</span>
</li>
{
typeof config.social.linkedin !== 'undefined' && config.social.linkedin && (
<li>
<span>
<div>
<GrLinkedinOption className="mr-2"/>
</div>
<div>
<a
href={`https://www.linkedin.com/in/${config.social.linkedin}`}
target="_blank"
rel="noreferrer"
className="text-base-content-important"
>
{config.social.linkedin}
</a>
</div>
</span>
</li>
)
}
{
typeof config.social.twitter !== 'undefined' && config.social.twitter && (
<li>
<span>
<div>
<SiTwitter className="mr-2"/>
</div>
<div>
<a
href={`https://twitter.com/${config.social.twitter}`}
target="_blank"
rel="noreferrer"
className="text-base-content-important"
>
{config.social.twitter}
</a>
</div>
</span>
</li>
)
}
{
typeof config.social.dribbble !== 'undefined' && config.social.dribbble && (
<li>
<span>
<div>
<CgDribbble className="mr-2"/>
</div>
<div>
<a
href={`https://dribbble.com/${config.social.dribbble}`}
target="_blank"
rel="noreferrer"
className="text-base-content-important"
>
{config.social.dribbble}
</a>
</div>
</span>
</li>
)
}
{
typeof config.social.behance !== 'undefined' && config.social.behance && (
<li>
<span>
<div>
<FaBehanceSquare className="mr-2"/>
</div>
<div>
<a
href={`https://www.behance.net/${config.social.behance}`}
target="_blank"
rel="noreferrer"
className="text-base-content-important"
>
{config.social.behance}
</a>
</div>
</span>
</li>
)
}
{
typeof config.social.facebook !== 'undefined' && config.social.facebook && (
<li>
<span>
<div>
<FaFacebook className="mr-2"/>
</div>
<div>
<a
href={`https://www.facebook.com/${config.social.facebook}`}
target="_blank"
rel="noreferrer"
className="text-base-content-important"
>
{config.social.facebook}
</a>
</div>
</span>
</li>
)
}
{
typeof config.social.medium !== 'undefined' && config.social.medium && (
<li>
<span>
<div>
<AiFillMediumSquare className="mr-2"/>
</div>
<div>
<a
href={`https://medium.com/@${config.social.medium}`}
target="_blank"
rel="noreferrer"
className="text-base-content-important"
>
{config.social.medium}
</a>
</div>
</span>
</li>
)
}
{
typeof config.social.devto !== 'undefined' && config.social.devto && (
<li>
<span>
<div>
<FaDev className="mr-2"/>
</div>
<div>
<a
href={`https://dev.to/${config.social.devto}`}
target="_blank"
rel="noreferrer"
className="text-base-content-important"
>
{config.social.devto}
</a>
</div>
</span>
</li>
)
}
{
typeof config.social.website !== 'undefined' && config.social.website && (
<li>
<span>
<div>
<FaGlobe className="mr-2"/>
</div>
<div>
<a
href={`${config.social.website}`}
target="_blank"
rel="noreferrer"
className="text-base-content-important"
>
{config.social.website}
</a>
</div>
</span>
</li>
)
}
{
typeof config.social.phone !== 'undefined' && config.social.phone && (
<li>
<span>
<div>
<RiPhoneFill className="mr-2"/>
</div>
<div>
<a
href={`tel:${config.social.phone}`}
rel="noreferrer"
className="text-base-content-important"
>
{config.social.phone}
</a>
</div>
</span>
</li>
)
}
{
typeof config.social.email !== 'undefined' && config.social.email && (
<li>
<span>
<div>
<MdMail className="mr-2"/>
</div>
<div>
<a
href={`mailto:${config.social.email}`}
target="_blank"
rel="noreferrer"
className="text-base-content-important"
>
{config.social.email}
</a>
</div>
</span>
</li>
)
}
</>
)
}
</ul>
</div>
</div>
)
}
Details.propTypes = {
profile: PropTypes.object
}
export default Details;

View File

@@ -1,91 +0,0 @@
import config from "../config";
import { GoPrimitiveDot } from 'react-icons/go';
import { skeleton } from "../helpers/utils";
import { useContext } from "react";
import { LoadingContext } from "../contexts/LoadingContext";
const Education = () => {
const [loading] = useContext(LoadingContext);
const renderSkeleton = () => {
let array = [];
for (let index = 0; index < 2; index++) {
array.push((
<li key={index}>
<span>
{skeleton({ width: 'w-2', height: 'h-2', className: "mr-2" })}
<div className="w-full">
<div className="block justify-between">
<div>
{skeleton({ width: 'w-9/12', height: 'h-4', className: "mb-2" })}
</div>
<div>
{skeleton({ width: 'w-6/12', height: 'h-4', className: "mb-2" })}
</div>
</div>
<div>
{skeleton({ width: 'w-6/12', height: 'h-3' })}
</div>
</div>
</span>
</li>
))
}
return array;
}
return (
<>
{
(typeof config.education !== 'undefined' && config.education.length !== 0) && (
<div className="card shadow-lg compact bg-base-100">
<div className="card-body">
<ul className="menu row-span-3 bg-base-100 text-base-content">
<li>
<div className="pb-0-important mx-3">
<h5 className="card-title">
{
loading ? skeleton({width: 'w-32', height: 'h-8'}) : (
<span className="opacity-70">Education</span>
)
}
</h5>
</div>
</li>
{
loading ? renderSkeleton() : (
config.education.map((item, index) => (
<li key={index}>
<span>
<div>
<GoPrimitiveDot className="mr-2 opacity-40"/>
</div>
<div>
<div className="block justify-between">
<div className="font-medium opacity-70">
{item.institution}
</div>
<div className="opacity-50">
{item.from} - {item.to}
</div>
</div>
<div className="opacity-70">
{item.degree}
</div>
</div>
</span>
</li>
))
)
}
</ul>
</div>
</div>
)
}
</>
)
}
export default Education;

View File

@@ -1,27 +0,0 @@
import PropTypes from 'prop-types';
const ErrorPage = (props) => {
return (
<div className="min-w-screen min-h-screen bg-base-200 flex items-center p-5 lg:p-20 overflow-hidden relative">
<div className="flex-1 min-h-full min-w-full rounded-3xl bg-base-100 shadow-xl p-10 lg:p-20 text-gray-800 relative md:flex items-center text-center md:text-left">
<div className="w-full">
<div className="mb-10 md:mb-20 mt-10 md:mt-20 text-gray-600 font-light">
<h1 className="font-black uppercase text-3xl lg:text-5xl text-primary mb-10">{props.status}</h1>
<p className="text-lg pb-2 text-base-content">{props.title}</p>
<p className="text-base-content text-opacity-60">{props.subTitle}</p>
</div>
</div>
</div>
<div className="w-64 md:w-96 h-96 md:h-full bg-accent bg-opacity-10 absolute -top-64 md:-top-96 right-20 md:right-32 rounded-full pointer-events-none -rotate-45 transform"></div>
<div className="w-96 h-full bg-secondary bg-opacity-10 absolute -bottom-96 right-64 rounded-full pointer-events-none -rotate-45 transform"></div>
</div>
)
}
ErrorPage.propTypes = {
status: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
subTitle: PropTypes.string.isRequired
}
export default ErrorPage;

View File

@@ -1,91 +0,0 @@
import config from "../config";
import { GoPrimitiveDot } from 'react-icons/go';
import { skeleton } from "../helpers/utils";
import { useContext } from "react";
import { LoadingContext } from "../contexts/LoadingContext";
const Experience = () => {
const [loading] = useContext(LoadingContext);
const renderSkeleton = () => {
let array = [];
for (let index = 0; index < 2; index++) {
array.push((
<li key={index}>
<span>
{skeleton({ width: 'w-2', height: 'h-2', className: "mr-2" })}
<div className="w-full">
<div className="block justify-between">
<div>
{skeleton({ width: 'w-9/12', height: 'h-4', className: "mb-2" })}
</div>
<div>
{skeleton({ width: 'w-6/12', height: 'h-4', className: "mb-2" })}
</div>
</div>
<div>
{skeleton({ width: 'w-6/12', height: 'h-3' })}
</div>
</div>
</span>
</li>
))
}
return array;
}
return (
<>
{
(typeof config.experiences !== 'undefined' && config.experiences.length !== 0) && (
<div className="card shadow-lg compact bg-base-100">
<div className="card-body">
<ul className="menu row-span-3 bg-base-100 text-base-content">
<li>
<div className="pb-0-important mx-3">
<h5 className="card-title">
{
loading ? skeleton({width: 'w-32', height: 'h-8'}) : (
<span className="opacity-70">Experience</span>
)
}
</h5>
</div>
</li>
{
loading ? renderSkeleton() : (
config.experiences.map((experience, index) => (
<li key={index}>
<span>
<div>
<GoPrimitiveDot className="mr-2 opacity-40"/>
</div>
<div>
<div className="block justify-between">
<div className="font-medium opacity-70">
{experience.company}
</div>
<div className="opacity-50">
{experience.from} - {experience.to}
</div>
</div>
<div className="opacity-70">
{experience.position}
</div>
</div>
</span>
</li>
))
)
}
</ul>
</div>
</div>
)
}
</>
)
}
export default Experience;

View File

@@ -0,0 +1,293 @@
import axios from 'axios';
import { Fragment, useCallback, useEffect, useState } from 'react';
import moment from 'moment';
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 Education from './education';
import Project from './project';
import Blog from './blog';
import {
genericError,
getInitialTheme,
noConfigError,
notFoundError,
setupHotjar,
tooManyRequestError,
sanitizeConfig,
} from '../helpers/utils';
import { HelmetProvider } from 'react-helmet-async';
import PropTypes from 'prop-types';
import '../assets/index.css';
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);
})
.then(() => {
let excludeRepo = ``;
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 = moment(
new Date(error.response.headers['x-ratelimit-reset'] * 1000)
).fromNow();
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 bg-base-200">
<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} />
<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}
/>
</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}
/>
<Blog
loading={loading}
googleAnalytics={sanitizedConfig.googleAnalytics}
blog={sanitizedConfig.blog}
/>
</div>
</div>
</div>
</div>
{/* The below attribution notice shall be
included in all copies or substantial portions of the Software. */}
{/* DO NOT REMOVE/MODIFY THE BELOW FOOTER. */}
{/* SEE 4(C) SECTION OF THE LICENSE FOR MORE DETAILS. */}
{/* https://github.com/arifszn/gitprofile/blob/main/LICENSE */}
<footer className="p-4 footer bg-base-200 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>
<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,
dribbble: PropTypes.string,
behance: PropTypes.string,
medium: PropTypes.string,
dev: PropTypes.string,
website: PropTypes.string,
phone: PropTypes.string,
email: PropTypes.string,
}),
skills: PropTypes.array,
experiences: PropTypes.arrayOf(
PropTypes.shape({
company: PropTypes.string,
position: PropTypes.string,
from: PropTypes.string,
to: 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,
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;

View File

@@ -1,30 +0,0 @@
import { useState, Fragment, useEffect } from 'react';
const LazyImage = ({ placeholder, src, alt, ...rest }) => {
const [loading, setLoading] = useState(true);
useEffect(() => {
const imageToLoad = new Image();
imageToLoad.src = src;
imageToLoad.onload = () => {
setLoading(false);
}
}, [src])
return (
<Fragment>
{
loading ? placeholder : (
<img
src={src}
alt={alt}
{...rest}
/>
)
}
</Fragment>
)
}
export default LazyImage;

View File

@@ -1,66 +0,0 @@
import React, { Fragment, useContext } from 'react';
import { Helmet } from "react-helmet-async";
import config from '../config';
import { isThemeDarkish } from '../helpers/utils';
import PropTypes from 'prop-types';
import { ThemeContext } from '../contexts/ThemeContext';
const MetaTags = (props) => {
const [theme] = useContext(ThemeContext);
return (
<Fragment>
{
props.profile && (
<Helmet>
{
config.googleAnalytics?.id && (
<script async src={`https://www.googletagmanager.com/gtag/js?id=${config.googleAnalytics.id}`}></script>
)
}
{
config.googleAnalytics?.id && (
<script>
{
`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${config.googleAnalytics.id}');
`
}
</script>
)
}
<title>Portfolio of {props.profile.name}</title>
<meta name="theme-color" content={isThemeDarkish(theme) ? '#000000' : '#ffffff'}/>
<meta name="description" content={props.profile.bio} />
<meta itemprop="name" content={`Portfolio of ${props.profile.name}`} />
<meta itemprop="description" content={props.profile.bio} />
<meta itemprop="image" content={props.profile.avatar} />
<meta property="og:url" content={typeof config.social.website !== 'undefined' ? config.social.website : ''} />
<meta property="og:type" content="website" />
<meta property="og:title" content={`Portfolio of ${props.profile.name}`} />
<meta property="og:description" content={props.profile.bio} />
<meta property="og:image" content={props.profile.avatar} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={`Portfolio of ${props.profile.name}`} />
<meta name="twitter:description" content={props.profile.bio} />
<meta name="twitter:image" content={props.profile.avatar} />
</Helmet>
)
}
</Fragment>
)
}
MetaTags.propTypes = {
profile: PropTypes.object
}
export default MetaTags;

View File

@@ -1,162 +0,0 @@
import { Fragment, useContext } from "react";
import { ga, languageColor, skeleton } from "../helpers/utils";
import { AiOutlineStar, AiOutlineFork } from 'react-icons/ai';
import config from "../config";
import PropTypes from 'prop-types';
import { LoadingContext } from "../contexts/LoadingContext";
const Project = (props) => {
const [loading] = useContext(LoadingContext);
const renderSkeleton = () => {
let array = [];
for (let index = 0; index < config.github.limit; index++) {
array.push((
<div className="card shadow-lg compact bg-base-100" key={index}>
<div className="flex justify-between flex-col p-8 h-full w-full">
<div>
<div className="flex items-center">
<span>
<h5 className="card-title text-lg">
{skeleton({ width: 'w-32', height: 'h-8' })}
</h5>
</span>
</div>
<div className="mb-5 mt-1">
{skeleton({ width: 'w-full', height: 'h-4', className: 'mb-2' })}
{skeleton({ width: 'w-full', height: 'h-4' })}
</div>
</div>
<div className="flex justify-between">
<div className="flex flex-grow">
<span className="mr-3 flex items-center">
{skeleton({ width: 'w-12', height: 'h-4' })}
</span>
<span className="flex items-center">
{skeleton({ width: 'w-12', height: 'h-4' })}
</span>
</div>
<div>
<span className="flex items-center">
{skeleton({ width: 'w-12', height: 'h-4' })}
</span>
</div>
</div>
</div>
</div>
))
}
return array;
}
const renderProjects = () => {
return props.repo.map((item, index) => (
<div
className="card shadow-lg compact bg-base-100 cursor-pointer"
key={index}
onClick={() => {
try {
if (config.googleAnalytics?.id) {
ga.event({
action: "Click project",
params: {
project: item.name
}
});
}
} catch (error) {
console.error(error);
}
window.open(item.html_url, '_blank')
}}
>
<div className="flex justify-between flex-col p-8 h-full w-full">
<div>
<div className="flex items-center opacity-60">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" className="inline-block w-5 h-5 mr-2 stroke-current"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"></path></svg>
<span>
<h5 className="card-title text-lg">
{item.name}
</h5>
</span>
</div>
<p className="mb-5 mt-1 text-base-content text-opacity-60 text-sm">
{item.description}
</p>
</div>
<div className="flex justify-between text-sm text-base-content text-opacity-60">
<div className="flex flex-grow">
<span className="mr-3 flex items-center">
<AiOutlineStar className="mr-0.5" />
<span>{item.stargazers_count}</span>
</span>
<span className="flex items-center">
<AiOutlineFork className="mr-0.5" />
<span>{item.forks_count}</span>
</span>
</div>
<div>
<span className="flex items-center">
<div className="w-3 h-3 rounded-full mr-1 opacity-60" style={{ backgroundColor: languageColor(item.language) }} />
<span>{item.language}</span>
</span>
</div>
</div>
</div>
</div>
));
}
return (
<Fragment>
<div className="col-span-1 lg:col-span-2">
<div className="grid grid-cols-2 gap-6">
<div className="col-span-2">
<div className="card compact bg-base-100 shadow-sm">
<div className="card-body">
<ul className="menu row-span-3 bg-base-100 text-base-content">
<li>
<div className="pb-0-important mx-4 flex items-center justify-between">
<h5 className="card-title">
{
loading ? skeleton({ width: 'w-28', height: 'h-8' }) : (
<span className="opacity-70">My Projects</span>
)
}
</h5>
{
loading ? skeleton({ width: 'w-10', height: 'h-5' }) : (
<a
href={`https://github.com/${config.github.username}?tab=repositories`}
target="_blank"
rel="noreferrer"
className="opacity-50"
>
See All
</a>
)
}
</div>
</li>
</ul>
</div>
</div>
</div>
<div className="col-span-2">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{(loading || !props.repo) ? renderSkeleton() : renderProjects()}
</div>
</div>
</div>
</div>
</Fragment>
)
}
Project.propTypes = {
repo: PropTypes.array
}
export default Project;

View File

@@ -1,58 +0,0 @@
import { useContext } from "react";
import config from "../config";
import { LoadingContext } from "../contexts/LoadingContext";
import { skeleton } from "../helpers/utils";
const Skill = () => {
const [loading] = useContext(LoadingContext);
const renderSkeleton = () => {
let array = [];
for (let index = 0; index < 12; index++) {
array.push((
<div key={index}>
{skeleton({ width: 'w-16', height: 'h-4', className: 'm-1' })}
</div>
))
}
return array;
}
return (
<>
{
(typeof config.skills !== 'undefined' && config.skills.length !== 0) && (
<div className="card shadow-lg compact bg-base-100">
<div className="card-body">
<div className="mx-3">
<h5 className="card-title">
{
loading ? skeleton({width: 'w-32', height: 'h-8'}) : (
<span className="opacity-70">Tech Stack</span>
)
}
</h5>
</div>
<div className="p-3 flow-root">
<div className="-m-1 flex flex-wrap">
{
loading ? renderSkeleton() : (
config.skills.map((skill, index) => (
<div key={index} className="m-1 text-xs inline-flex items-center font-bold leading-sm uppercase px-3 py-1 badge-primary bg-opacity-75 rounded-full">
{skill}
</div>
))
)
}
</div>
</div>
</div>
</div>
)
}
</>
)
}
export default Skill;

View File

@@ -1,78 +0,0 @@
import config from '../config';
import { skeleton } from '../helpers/utils';
import { AiOutlineControl } from 'react-icons/ai';
import { useContext } from 'react';
import { ThemeContext } from '../contexts/ThemeContext';
import { LoadingContext } from '../contexts/LoadingContext';
const ThemeChanger = () => {
const [theme, setTheme] = useContext(ThemeContext);
const [loading] = useContext(LoadingContext);
const changeTheme = (e, selectedTheme) => {
e.preventDefault();
document.querySelector('html').setAttribute('data-theme', selectedTheme);
localStorage.setItem('ezprofile-theme', selectedTheme);
setTheme(selectedTheme);
}
return (
<div className="card overflow-visible shadow-lg compact bg-base-100">
<div className="flex-row items-center space-x-4 flex pl-6 pr-2 py-4">
<div className="flex-1">
<h5 className="card-title">
{
loading ? skeleton({ width: 'w-20', height: 'h-8' }) : (
<span className="opacity-70">Theme</span>
)
}
</h5>
<span className="text-base-content text-opacity-40 capitalize text-sm">
{
loading ? skeleton({ width: 'w-16', height: 'h-5' }) : (theme === config.themeConfig.default ? 'Default' : theme)
}
</span>
</div>
<div className="flex-0">
{
loading ? skeleton({ width: 'w-14 md:w-28', height: 'h-10', className: 'mr-6' }) : (
<div title="Change Theme" className="dropdown dropdown-end">
<div tabIndex={0} className="btn btn-ghost m-1 normal-case opacity-50">
<AiOutlineControl className="inline-block w-5 h-5 stroke-current md:mr-2"/>
<span className="hidden md:inline">
Change Theme
</span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1792 1792" className="inline-block w-4 h-4 ml-1 fill-current">
<path d="M1395 736q0 13-10 23l-466 466q-10 10-23 10t-23-10l-466-466q-10-10-10-23t10-23l50-50q10-10 23-10t23 10l393 393 393-393q10-10 23-10t23 10l50 50q10 10 10 23z" />
</svg>
</div>
<div tabIndex={0} className="mt-16 overflow-y-auto shadow-2xl top-px dropdown-content h-96 w-52 rounded-b-box bg-base-200 text-base-content">
<ul className="p-4 menu compact">
{
[config.themeConfig.default, ...config.themeConfig.themes.filter(item => item !== config.themeConfig.default)].map((item, index) => (
<li key={index}>
{/* eslint-disable-next-line */}
<a
onClick={(e) => changeTheme(e, item)}
className={`${theme === item ? 'active' : ''}`}
>
<span className="opacity-60 capitalize">
{item === config.themeConfig.default ? 'Default' : item}
</span>
</a>
</li>
))
}
</ul>
</div>
</div>
)
}
</div>
</div>
</div>
)
}
export default ThemeChanger;

View File

@@ -0,0 +1,60 @@
import PropTypes from 'prop-types';
import { fallbackImage, skeleton } from '../../helpers/utils';
import LazyImage from '../lazy-image';
const AvatarCard = ({ profile, loading }) => {
return (
<div className="card shadow-lg compact bg-base-100">
<div className="grid place-items-center py-8">
{loading || !profile ? (
<div className="avatar opacity-90">
<div className="mb-8 rounded-full w-32 h-32">
{skeleton({
width: 'w-full',
height: 'h-full',
shape: '',
})}
</div>
</div>
) : (
<div className="avatar opacity-90">
<div className="mb-8 rounded-full w-32 h-32 ring ring-primary ring-offset-base-100 ring-offset-2">
{
<LazyImage
src={profile.avatar ? profile.avatar : fallbackImage}
alt={profile.name}
placeholder={skeleton({
width: 'w-full',
height: 'h-full',
shape: '',
})}
/>
}
</div>
</div>
)}
<div className="text-center mx-auto px-8">
<h5 className="font-bold text-2xl">
{loading || !profile ? (
skeleton({ width: 'w-48', height: 'h-8' })
) : (
<span className="opacity-70">{profile.name}</span>
)}
</h5>
<div className="mt-3 text-base-content text-opacity-60 font-mono">
{loading || !profile
? skeleton({ width: 'w-48', height: 'h-5' })
: profile.bio}
</div>
</div>
</div>
</div>
);
};
AvatarCard.propTypes = {
profile: PropTypes.object,
loading: PropTypes.bool.isRequired,
};
export default AvatarCard;

View File

@@ -0,0 +1,226 @@
import moment from 'moment';
import { Fragment, useEffect, useState } from 'react';
import { ga, skeleton } from '../../helpers/utils';
import LazyImage from '../lazy-image';
import PropTypes from 'prop-types';
import { AiOutlineContainer } from 'react-icons/ai';
import { getDevPost, getMediumPost } from '@arifszn/blog-js';
const displaySection = (blog) => {
if (
typeof blog !== 'undefined' &&
typeof blog.source !== 'undefined' &&
typeof blog.username !== 'undefined' &&
blog.source &&
blog.username
) {
return true;
} else {
return false;
}
};
const Blog = ({ loading, blog, googleAnalytics }) => {
const [articles, setArticles] = useState(null);
useEffect(() => {
if (displaySection(blog)) {
if (blog.source === 'medium') {
getMediumPost({
user: blog.username,
}).then((res) => {
setArticles(res);
});
} else if (blog.source === 'dev') {
getDevPost({
user: blog.username,
}).then((res) => {
setArticles(res);
});
}
}
}, []);
const renderSkeleton = () => {
let array = [];
for (let index = 0; index < blog.limit; index++) {
array.push(
<div className="card shadow-lg compact bg-base-100" key={index}>
<div className="p-8 h-full w-full">
<div className="flex items-center flex-col md:flex-row">
<div className="avatar mb-5 md:mb-0">
<div className="w-24 h-24 mask mask-squircle">
{skeleton({
width: 'w-full',
height: 'h-full',
shape: '',
})}
</div>
</div>
<div className="w-full">
<div className="flex items-start px-4">
<div className="w-full">
<h2>
{skeleton({
width: 'w-full',
height: 'h-8',
className: 'mb-2 mx-auto md:mx-0',
})}
</h2>
{skeleton({
width: 'w-24',
height: 'h-3',
className: 'mx-auto md:mx-0',
})}
<div className="mt-3">
{skeleton({
width: 'w-full',
height: 'h-4',
className: 'mx-auto md:mx-0',
})}
</div>
<div className="mt-4 flex items-center flex-wrap justify-center md:justify-start">
{skeleton({
width: 'w-32',
height: 'h-4',
className: 'md:mr-2 mx-auto md:mx-0',
})}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
return array;
};
const renderArticles = () => {
return articles && articles.length ? (
articles.slice(0, blog.limit).map((article, index) => (
<a
className="card shadow-lg compact bg-base-100 cursor-pointer"
key={index}
href={article.link}
onClick={(e) => {
e.preventDefault();
try {
if (googleAnalytics?.id) {
ga.event({
action: 'Click Blog Post',
params: {
post: article.title,
},
});
}
} catch (error) {
console.error(error);
}
typeof window !== 'undefined' &&
window.open(article.link, '_blank');
}}
>
<div className="p-8 h-full w-full">
<div className="flex items-center flex-col md:flex-row">
<div className="avatar mb-5 md:mb-0 opacity-90">
<div className="w-24 h-24 mask mask-squircle">
<LazyImage
src={article.thumbnail}
alt={'thumbnail'}
placeholder={skeleton({
width: 'w-full',
height: 'h-full',
shape: '',
})}
/>
</div>
</div>
<div className="w-full">
<div className="flex items-start px-4">
<div className="text-center md:text-left w-full">
<h2 className="font-semibold text-base-content opacity-60">
{article.title}
</h2>
<p className="opacity-50 text-xs">
{moment(article.publishedAt).fromNow()}
</p>
<p className="mt-3 text-base-content text-opacity-60 text-sm">
{article.description}
</p>
<div className="mt-4 flex items-center flex-wrap justify-center md:justify-start">
{article.categories.map((category, index2) => (
<div
className="py-2 px-4 text-xs leading-3 rounded-full bg-base-300 mr-1 mb-1 opacity-50"
key={index2}
>
#{category}
</div>
))}
</div>
</div>
</div>
</div>
</div>
</div>
</a>
))
) : (
<div className="text-center mb-6">
<AiOutlineContainer className="mx-auto h-12 w-12 opacity-30" />
<p className="mt-1 text-sm opacity-50">No recent post</p>
</div>
);
};
return (
<Fragment>
{displaySection(blog) && (
<div className="col-span-1 lg:col-span-2">
<div className="grid grid-cols-2 gap-6">
<div className="col-span-2">
<div
className={`card compact ${
loading || (articles && articles.length)
? 'bg-gradient-to-br to-base-200 from-base-100 shadow'
: 'bg-base-100 shadow-lg'
}`}
>
<div className="card-body">
<div className="mx-3 mb-2">
<h5 className="card-title">
{loading ? (
skeleton({ width: 'w-28', height: 'h-8' })
) : (
<span className="opacity-70">Recent Posts</span>
)}
</h5>
</div>
<div className="col-span-2">
<div className="grid grid-cols-1 gap-6">
{loading || !articles
? renderSkeleton()
: renderArticles()}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)}
</Fragment>
);
};
Blog.propTypes = {
loading: PropTypes.bool.isRequired,
blog: PropTypes.object.isRequired,
googleAnalytics: PropTypes.object.isRequired,
};
export default Blog;

View File

@@ -0,0 +1,194 @@
import { MdLocationOn, MdMail } from 'react-icons/md';
import { AiFillGithub, 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 { Fragment } from 'react';
import {
FaBehanceSquare,
FaBuilding,
FaDev,
FaFacebook,
FaGlobe,
} from 'react-icons/fa';
import PropTypes from 'prop-types';
import { skeleton } from '../../helpers/utils';
const ListItem = ({ icon, title, value, link, skeleton = false }) => {
return (
<a
href={link}
target="_blank"
rel="noreferrer"
className="flex justify-start py-2 px-1 items-center"
>
<span className="w-2 m-2">{icon}</span>
<div className="flex-grow font-medium px-2">{title}</div>
<div
className={`${
skeleton ? 'flex-grow' : ''
} text-sm font-normal text-right mr-2 ml-3 ${link ? 'truncate' : ''}`}
>
<div
style={{
wordBreak: 'break-word',
}}
>
{value}
</div>
</div>
</a>
);
};
const Details = ({ profile, loading, social, github }) => {
const renderSkeleton = () => {
let array = [];
for (let index = 0; index < 4; index++) {
array.push(
<ListItem
key={index}
skeleton={true}
icon={skeleton({ width: 'w-4', height: 'h-4' })}
title={skeleton({ width: 'w-24', height: 'h-4' })}
value={skeleton({ width: 'w-full', height: 'h-4' })}
/>
);
}
return array;
};
return (
<div className="card shadow-lg compact bg-base-100">
<div className="card-body">
<div className="text-base-content text-opacity-60">
{loading || !profile ? (
renderSkeleton()
) : (
<Fragment>
{profile.location && (
<ListItem
icon={<MdLocationOn className="mr-2" />}
title="Based in:"
value={profile.location}
/>
)}
{profile.company && (
<ListItem
icon={<FaBuilding className="mr-2" />}
title="Company:"
value={profile.company}
/>
)}
<ListItem
icon={<AiFillGithub className="mr-2" />}
title="GitHub:"
value={github.username}
link={`https://github.com/${github.username}`}
/>
{typeof social.twitter !== 'undefined' && social.twitter && (
<ListItem
icon={<SiTwitter className="mr-2" />}
title="Twitter:"
value={social.twitter}
link={`https://twitter.com/${social.twitter}`}
/>
)}
{typeof social.linkedin !== 'undefined' && social.linkedin && (
<ListItem
icon={<GrLinkedinOption className="mr-2" />}
title="LinkedIn:"
value={social.linkedin}
link={`https://www.linkedin.com/in/${social.linkedin}`}
/>
)}
{typeof social.dribbble !== 'undefined' && social.dribbble && (
<ListItem
icon={<CgDribbble className="mr-2" />}
title="Dribbble:"
value={social.dribbble}
link={`https://dribbble.com/${social.dribbble}`}
/>
)}
{typeof social.behance !== 'undefined' && social.behance && (
<ListItem
icon={<FaBehanceSquare className="mr-2" />}
title="Behance:"
value={social.behance}
link={`https://www.behance.net/${social.behance}`}
/>
)}
{typeof social.facebook !== 'undefined' && social.facebook && (
<ListItem
icon={<FaFacebook className="mr-2" />}
title="Facebook:"
value={social.facebook}
link={`https://www.facebook.com/${social.facebook}`}
/>
)}
{typeof social.medium !== 'undefined' && social.medium && (
<ListItem
icon={<AiFillMediumSquare className="mr-2" />}
title="Medium:"
value={social.medium}
link={`https://medium.com/@${social.medium}`}
/>
)}
{typeof social.dev !== 'undefined' && social.dev && (
<ListItem
icon={<FaDev className="mr-2" />}
title="Dev:"
value={social.dev}
link={`https://dev.to/${social.dev}`}
/>
)}
{typeof social.website !== 'undefined' && social.website && (
<ListItem
icon={<FaGlobe className="mr-2" />}
title="Website:"
value={social.website}
link={social.website}
/>
)}
{typeof social.phone !== 'undefined' && social.phone && (
<ListItem
icon={<RiPhoneFill className="mr-2" />}
title="Phone:"
value={social.phone}
link={`tel:${social.phone}`}
/>
)}
{typeof social.email !== 'undefined' && social.email && (
<ListItem
icon={<MdMail className="mr-2" />}
title="Email:"
value={social.email}
link={`mailto:${social.email}`}
/>
)}
</Fragment>
)}
</div>
</div>
</div>
);
};
Details.propTypes = {
profile: PropTypes.object,
loading: PropTypes.bool.isRequired,
social: PropTypes.object.isRequired,
github: PropTypes.object.isRequired,
};
ListItem.propTypes = {
icon: PropTypes.node,
title: PropTypes.node,
value: PropTypes.node,
link: PropTypes.string,
skeleton: PropTypes.bool,
};
export default Details;

View File

@@ -0,0 +1,91 @@
import { skeleton } from '../../helpers/utils';
import { Fragment } from 'react';
import PropTypes from 'prop-types';
const ListItem = ({ time, degree, institution }) => (
<li className="mb-5 ml-4">
<div
className="absolute w-2 h-2 bg-base-300 rounded-full border border-base-300 mt-1.5"
style={{ left: '-4.5px' }}
></div>
<div className="my-0.5 text-xs">{time}</div>
<h3 className="font-semibold">{degree}</h3>
<div className="mb-4 font-normal">{institution}</div>
</li>
);
const Education = ({ loading, education }) => {
const renderSkeleton = () => {
let array = [];
for (let index = 0; index < 2; index++) {
array.push(
<ListItem
key={index}
time={skeleton({
width: 'w-5/12',
height: 'h-4',
})}
degree={skeleton({
width: 'w-6/12',
height: 'h-4',
className: 'my-1.5',
})}
institution={skeleton({ width: 'w-6/12', height: 'h-3' })}
/>
);
}
return array;
};
return (
<>
{typeof education !== 'undefined' && education.length !== 0 && (
<div className="card shadow-lg compact bg-base-100">
<div className="card-body">
<div className="mx-3">
<h5 className="card-title">
{loading ? (
skeleton({ width: 'w-32', height: 'h-8' })
) : (
<span className="opacity-70">Education</span>
)}
</h5>
</div>
<div className="text-base-content text-opacity-60">
<ol className="relative border-l border-base-300 border-opacity-30 my-2 mx-4">
{loading ? (
renderSkeleton()
) : (
<Fragment>
{education.map((item, index) => (
<ListItem
key={index}
time={`${item.from} - ${item.to}`}
degree={item.degree}
institution={item.institution}
/>
))}
</Fragment>
)}
</ol>
</div>
</div>
</div>
)}
</>
);
};
Education.propTypes = {
loading: PropTypes.bool.isRequired,
education: PropTypes.array.isRequired,
};
ListItem.propTypes = {
time: PropTypes.node,
degree: PropTypes.node,
institution: PropTypes.node,
};
export default Education;

View File

@@ -0,0 +1,31 @@
import PropTypes from 'prop-types';
const ErrorPage = (props) => {
return (
<div className="min-w-screen min-h-screen bg-base-200 flex items-center p-5 lg:p-20 overflow-hidden relative">
<div className="flex-1 min-h-full min-w-full rounded-3xl bg-base-100 shadow-xl p-10 lg:p-20 text-gray-800 relative md:flex items-center text-center md:text-left">
<div className="w-full">
<div className="mb-10 md:mb-20 mt-10 md:mt-20 text-gray-600 font-light">
<h1 className="font-black uppercase text-3xl lg:text-5xl text-primary mb-10">
{props.status}
</h1>
<p className="text-lg pb-2 text-base-content">{props.title}</p>
<div className="text-base-content text-opacity-60">
{props.subTitle}
</div>
</div>
</div>
</div>
<div className="w-64 md:w-96 h-96 md:h-full bg-accent bg-opacity-10 absolute -top-64 md:-top-96 right-20 md:right-32 rounded-full pointer-events-none -rotate-45 transform"></div>
<div className="w-96 h-full bg-secondary bg-opacity-10 absolute -bottom-96 right-64 rounded-full pointer-events-none -rotate-45 transform"></div>
</div>
);
};
ErrorPage.propTypes = {
status: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
subTitle: PropTypes.node.isRequired,
};
export default ErrorPage;

View File

@@ -0,0 +1,91 @@
import { skeleton } from '../../helpers/utils';
import { Fragment } from 'react';
import PropTypes from 'prop-types';
const ListItem = ({ time, position, company }) => (
<li className="mb-5 ml-4">
<div
className="absolute w-2 h-2 bg-base-300 rounded-full border border-base-300 mt-1.5"
style={{ left: '-4.5px' }}
></div>
<div className="my-0.5 text-xs">{time}</div>
<h3 className="font-semibold">{position}</h3>
<div className="mb-4 font-normal">{company}</div>
</li>
);
const Experience = ({ experiences, loading }) => {
const renderSkeleton = () => {
let array = [];
for (let index = 0; index < 2; index++) {
array.push(
<ListItem
key={index}
time={skeleton({
width: 'w-5/12',
height: 'h-4',
})}
position={skeleton({
width: 'w-6/12',
height: 'h-4',
className: 'my-1.5',
})}
company={skeleton({ width: 'w-6/12', height: 'h-3' })}
/>
);
}
return array;
};
return (
<>
{typeof experiences !== 'undefined' && experiences.length !== 0 && (
<div className="card shadow-lg compact bg-base-100">
<div className="card-body">
<div className="mx-3">
<h5 className="card-title">
{loading ? (
skeleton({ width: 'w-32', height: 'h-8' })
) : (
<span className="opacity-70">Experience</span>
)}
</h5>
</div>
<div className="text-base-content text-opacity-60">
<ol className="relative border-l border-base-300 border-opacity-30 my-2 mx-4">
{loading ? (
renderSkeleton()
) : (
<Fragment>
{experiences.map((experience, index) => (
<ListItem
key={index}
time={`${experience.from} - ${experience.to}`}
position={experience.position}
company={experience.company}
/>
))}
</Fragment>
)}
</ol>
</div>
</div>
</div>
)}
</>
);
};
ListItem.propTypes = {
time: PropTypes.node,
position: PropTypes.node,
company: PropTypes.node,
};
Experience.propTypes = {
experiences: PropTypes.array.isRequired,
loading: PropTypes.bool.isRequired,
};
export default Experience;

View File

@@ -0,0 +1,65 @@
import { Fragment } from 'react';
import { Helmet } from 'react-helmet-async';
import PropTypes from 'prop-types';
import { isThemeDarkish } from '../../helpers/utils';
const HeadTagEditor = ({ profile, theme, googleAnalytics, social }) => {
return (
<Fragment>
{profile && (
<Helmet>
{googleAnalytics?.id && (
<script
async
src={`https://www.googletagmanager.com/gtag/js?id=${googleAnalytics.id}`}
></script>
)}
{googleAnalytics?.id && (
<script>
{`window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${googleAnalytics.id}');`}
</script>
)}
<title>Portfolio of {profile.name}</title>
<meta
name="theme-color"
content={isThemeDarkish(theme) ? '#000000' : '#ffffff'}
/>
<meta name="description" content={profile.bio} />
<meta itemProp="name" content={`Portfolio of ${profile.name}`} />
<meta itemProp="description" content={profile.bio} />
<meta itemProp="image" content={profile.avatar} />
<meta
property="og:url"
content={
typeof social.website !== 'undefined' ? social.website : ''
}
/>
<meta property="og:type" content="website" />
<meta property="og:title" content={`Portfolio of ${profile.name}`} />
<meta property="og:description" content={profile.bio} />
<meta property="og:image" content={profile.avatar} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={`Portfolio of ${profile.name}`} />
<meta name="twitter:description" content={profile.bio} />
<meta name="twitter:image" content={profile.avatar} />
</Helmet>
)}
</Fragment>
);
};
HeadTagEditor.propTypes = {
profile: PropTypes.object,
theme: PropTypes.string,
googleAnalytics: PropTypes.object.isRequired,
social: PropTypes.object.isRequired,
};
export default HeadTagEditor;

View File

@@ -0,0 +1,29 @@
import { useState, Fragment, useEffect } from 'react';
import PropTypes from 'prop-types';
const LazyImage = ({ placeholder, src, alt, ...rest }) => {
const [loading, setLoading] = useState(true);
useEffect(() => {
const imageToLoad = new Image();
imageToLoad.src = src;
imageToLoad.onload = () => {
setLoading(false);
};
}, [src]);
return (
<Fragment>
{loading ? placeholder : <img src={src} alt={alt} {...rest} />}
</Fragment>
);
};
LazyImage.propTypes = {
placeholder: PropTypes.node,
alt: PropTypes.string,
src: PropTypes.string,
};
export default LazyImage;

View File

@@ -0,0 +1,177 @@
import { Fragment } from 'react';
import { AiOutlineStar, AiOutlineFork } from 'react-icons/ai';
import PropTypes from 'prop-types';
import { ga, languageColor, skeleton } from '../../helpers/utils';
const Project = ({ repo, loading, github, googleAnalytics }) => {
const renderSkeleton = () => {
let array = [];
for (let index = 0; index < github.limit; index++) {
array.push(
<div className="card shadow-lg compact bg-base-100" key={index}>
<div className="flex justify-between flex-col p-8 h-full w-full">
<div>
<div className="flex items-center">
<span>
<h5 className="card-title text-lg">
{skeleton({ width: 'w-32', height: 'h-8' })}
</h5>
</span>
</div>
<div className="mb-5 mt-1">
{skeleton({
width: 'w-full',
height: 'h-4',
className: 'mb-2',
})}
{skeleton({ width: 'w-full', height: 'h-4' })}
</div>
</div>
<div className="flex justify-between">
<div className="flex flex-grow">
<span className="mr-3 flex items-center">
{skeleton({ width: 'w-12', height: 'h-4' })}
</span>
<span className="flex items-center">
{skeleton({ width: 'w-12', height: 'h-4' })}
</span>
</div>
<div>
<span className="flex items-center">
{skeleton({ width: 'w-12', height: 'h-4' })}
</span>
</div>
</div>
</div>
</div>
);
}
return array;
};
const renderProjects = () => {
return repo.map((item, index) => (
<a
className="card shadow-lg compact bg-base-100 cursor-pointer"
href={item.html_url}
key={index}
onClick={(e) => {
e.preventDefault();
try {
if (googleAnalytics?.id) {
ga.event({
action: 'Click project',
params: {
project: item.name,
},
});
}
} catch (error) {
console.error(error);
}
typeof window !== 'undefined' && window.open(item.html_url, '_blank');
}}
>
<div className="flex justify-between flex-col p-8 h-full w-full">
<div>
<div className="flex items-center opacity-60">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
className="inline-block w-5 h-5 mr-2 stroke-current"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
></path>
</svg>
<span>
<h5 className="card-title text-lg">{item.name}</h5>
</span>
</div>
<p className="mb-5 mt-1 text-base-content text-opacity-60 text-sm">
{item.description}
</p>
</div>
<div className="flex justify-between text-sm text-base-content text-opacity-60">
<div className="flex flex-grow">
<span className="mr-3 flex items-center">
<AiOutlineStar className="mr-0.5" />
<span>{item.stargazers_count}</span>
</span>
<span className="flex items-center">
<AiOutlineFork className="mr-0.5" />
<span>{item.forks_count}</span>
</span>
</div>
<div>
<span className="flex items-center">
<div
className="w-3 h-3 rounded-full mr-1 opacity-60"
style={{ backgroundColor: languageColor(item.language) }}
/>
<span>{item.language}</span>
</span>
</div>
</div>
</div>
</a>
));
};
return (
<Fragment>
<div className="col-span-1 lg:col-span-2">
<div className="grid grid-cols-2 gap-6">
<div className="col-span-2">
<div className="card compact bg-gradient-to-br to-base-200 from-base-100 shadow">
<div className="card-body">
<div className="mx-3 flex items-center justify-between mb-2">
<h5 className="card-title">
{loading ? (
skeleton({ width: 'w-28', height: 'h-8' })
) : (
<span className="opacity-70">My Projects</span>
)}
</h5>
{loading ? (
skeleton({ width: 'w-10', height: 'h-5' })
) : (
<a
href={`https://github.com/${github.username}?tab=repositories`}
target="_blank"
rel="noreferrer"
className="opacity-50"
>
See All
</a>
)}
</div>
<div className="col-span-2">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{loading || !repo ? renderSkeleton() : renderProjects()}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</Fragment>
);
};
Project.propTypes = {
repo: PropTypes.array,
loading: PropTypes.bool.isRequired,
github: PropTypes.object.isRequired,
googleAnalytics: PropTypes.object.isRequired,
};
export default Project;

View File

@@ -0,0 +1,58 @@
import { skeleton } from '../../helpers/utils';
import PropTypes from 'prop-types';
const Skill = ({ loading, skills }) => {
const renderSkeleton = () => {
let array = [];
for (let index = 0; index < 12; index++) {
array.push(
<div key={index}>
{skeleton({ width: 'w-16', height: 'h-4', className: 'm-1' })}
</div>
);
}
return array;
};
return (
<>
{typeof skills !== 'undefined' && skills.length !== 0 && (
<div className="card shadow-lg compact bg-base-100">
<div className="card-body">
<div className="mx-3">
<h5 className="card-title">
{loading ? (
skeleton({ width: 'w-32', height: 'h-8' })
) : (
<span className="opacity-70">Tech Stack</span>
)}
</h5>
</div>
<div className="p-3 flow-root">
<div className="-m-1 flex flex-wrap justify-center">
{loading
? renderSkeleton()
: skills.map((skill, index) => (
<div
key={index}
className="m-1 text-xs inline-flex items-center font-bold leading-sm px-3 py-1 badge-primary bg-opacity-90 rounded-full"
>
{skill}
</div>
))}
</div>
</div>
</div>
</div>
)}
</>
);
};
Skill.propTypes = {
loading: PropTypes.bool.isRequired,
skills: PropTypes.array.isRequired,
};
export default Skill;

View File

@@ -0,0 +1,98 @@
import { AiOutlineControl } from 'react-icons/ai';
import { skeleton } from '../../helpers/utils';
import PropTypes from 'prop-types';
const ThemeChanger = ({ theme, setTheme, loading, themeConfig }) => {
const changeTheme = (e, selectedTheme) => {
e.preventDefault();
document.querySelector('html').setAttribute('data-theme', selectedTheme);
typeof window !== 'undefined' &&
localStorage.setItem('gitprofile-theme', selectedTheme);
setTheme(selectedTheme);
};
return (
<div className="card overflow-visible shadow-lg compact bg-base-100">
<div className="flex-row items-center space-x-4 flex pl-6 pr-2 py-4">
<div className="flex-1">
<h5 className="card-title">
{loading ? (
skeleton({ width: 'w-20', height: 'h-8', className: 'mb-1' })
) : (
<span className="opacity-70">Theme</span>
)}
</h5>
<span className="text-base-content text-opacity-40 capitalize text-sm">
{loading
? skeleton({ width: 'w-16', height: 'h-5' })
: theme === themeConfig.defaultTheme
? 'Default'
: theme}
</span>
</div>
<div className="flex-0">
{loading ? (
skeleton({
width: 'w-14 md:w-28',
height: 'h-10',
className: 'mr-6',
})
) : (
<div title="Change Theme" className="dropdown dropdown-end">
<div
tabIndex={0}
className="btn btn-ghost m-1 normal-case opacity-50"
>
<AiOutlineControl className="inline-block w-5 h-5 stroke-current md:mr-2" />
<span className="hidden md:inline">Change Theme</span>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1792 1792"
className="inline-block w-4 h-4 ml-1 fill-current"
>
<path d="M1395 736q0 13-10 23l-466 466q-10 10-23 10t-23-10l-466-466q-10-10-10-23t10-23l50-50q10-10 23-10t23 10l393 393 393-393q10-10 23-10t23 10l50 50q10 10 10 23z" />
</svg>
</div>
<div
tabIndex={0}
className="mt-16 overflow-y-auto shadow-2xl top-px dropdown-content max-h-96 w-52 rounded-b-box bg-base-200 text-base-content"
>
<ul className="p-4 menu compact">
{[
themeConfig.defaultTheme,
...themeConfig.themes.filter(
(item) => item !== themeConfig.defaultTheme
),
].map((item, index) => (
<li key={index}>
{/* eslint-disable-next-line */}
<a
onClick={(e) => changeTheme(e, item)}
className={`${theme === item ? 'active' : ''}`}
>
<span className="opacity-60 capitalize">
{item === themeConfig.defaultTheme ? 'Default' : item}
</span>
</a>
</li>
))}
</ul>
</div>
</div>
)}
</div>
</div>
</div>
);
};
ThemeChanger.propTypes = {
theme: PropTypes.string,
setTheme: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired,
themeConfig: PropTypes.object.isRequired,
};
export default ThemeChanger;

View File

@@ -1,128 +0,0 @@
// config.js
module.exports = {
github: {
username: 'arifszn', // Your GitHub org/user name. (Required)
sortBy: 'stars', // stars | updated
limit: 8, // How many projects to display.
exclude: {
forks: false, // Forked projects will not be displayed if set to true.
projects: ['laravel-ecommerce'] // These projects will not be displayed. example: ['my-project1', 'my-project2']
}
},
social: {
linkedin: 'ariful-alam',
twitter: 'arif_swozon',
facebook: '',
dribbble: '',
behance: '',
medium: '',
devto: 'arifszn',
website: 'https://arifszn.github.io',
phone: '',
email: 'arifulalamszn@gmail.com'
},
skills: [
'PHP',
'Laravel',
'JavaScript',
'React.js',
'Node.js',
'MySQL',
'Git',
'Docker',
'CSS',
'Antd',
'Tailwind',
'Bootstrap',
],
experiences: [
{
company: 'Monstarlab Bangladesh',
position: 'Backend Engineer II',
from: 'September 2021',
to: 'Present'
},
{
company: 'Orangetoolz',
position: 'Jr. Full Stack Engineer',
from: 'July 2019',
to: 'August 2021'
},
{
company: 'Techvillage',
position: 'Jr. Software Engineer',
from: 'January 2019',
to: ' June 2019'
}
],
education: [
{
institution: 'American International University-Bangladesh',
degree: 'Bachelor of Science',
from: '2015',
to: '2019'
},
{
institution: 'Cantonment College, Jessore',
degree: 'Higher Secondary Certificate (HSC)',
from: '2012',
to: '2014',
},
{
institution: 'Chowgacha Shahadat Pilot High School',
degree: 'Secondary School Certificate (SSC)',
from: '2007',
to: '2012'
}
],
blog: {
// Display blog posts from your medium or dev.to account. (Optional)
source: 'dev.to', // medium | dev.to
username: 'arifszn',
limit: 3 // How many posts to display. Max is 10.
},
googleAnalytics: {
// GA3 tracking id/GA4 tag id UA-XXXXXXXXX-X | G-XXXXXXXXXX
id: 'G-WLLB5E14M6' // Please remove this and use your own tag id or keep it empty
},
hotjar: {
id: '2617601', // Please remove this and use your own id or keep it empty
snippetVersion : 6
},
themeConfig: {
default: 'light',
// Hides the switch in the navbar
// Useful if you want to support a single color mode
disableSwitch: false,
// Should we use the prefers-color-scheme media-query,
// using user system preferences, instead of the hardcoded default
respectPrefersColorScheme: true,
// Available themes. To remove any theme, exclude from here.
themes: [
'light',
'dark',
'cupcake',
'bumblebee',
'emerald',
'corporate',
'synthwave',
'retro',
'cyberpunk',
'valentine',
'halloween',
'garden',
'forest',
'aqua',
'lofi',
'pastel',
'fantasy',
'wireframe',
'black',
'luxury',
'dracula'
]
}
}

View File

@@ -1,15 +0,0 @@
import { createContext, useState } from "react";
const initialValue = true;
export const LoadingContext = createContext();
export const LoadingProvider = (props) => {
const [loading, setLoading] = useState(initialValue);
return (
<LoadingContext.Provider value={[loading, setLoading]}>
{props.children}
</LoadingContext.Provider>
);
}

View File

@@ -1,16 +0,0 @@
import { createContext, useState } from "react";
import { getInitialTheme } from "../helpers/utils";
const initialValue = getInitialTheme();
export const ThemeContext = createContext();
export const ThemeProvider = (props) => {
const [theme, setTheme] = useState(initialValue);
return (
<ThemeContext.Provider value={[theme, setTheme]}>
{props.children}
</ThemeContext.Provider>
);
}

1434
src/data/colors.json Normal file

File diff suppressed because it is too large Load Diff

15
src/favicon.svg Normal file
View File

@@ -0,0 +1,15 @@
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
<stop stop-color="#41D1FF"/>
<stop offset="1" stop-color="#BD34FE"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFEA83"/>
<stop offset="0.0833333" stop-color="#FFDD35"/>
<stop offset="1" stop-color="#FFA800"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -1,79 +0,0 @@
import config from "../config";
import colors from './colors.json';
import { hotjar } from 'react-hotjar';
export const getInitialTheme = () => {
if (config.themeConfig.disableSwitch) {
return config.themeConfig.default;
}
if (localStorage.hasOwnProperty('ezprofile-theme')) {
let theme = localStorage.getItem('ezprofile-theme');
return theme;
}
if (config.themeConfig.respectPrefersColorScheme && !config.themeConfig.disableSwitch) {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : config.themeConfig.default;
}
return config.themeConfig.default;
}
export const skeleton = ({width = null, height = null, style = {}, shape = 'rounded-full', className = null}) => {
return <div className={`bg-base-300 animate-pulse ${shape}${className ? ` ${className}` : ''}${width ? ` ${width}` : ''}${height ? ` ${height}` : ''}`} style={style}/>;
}
export const languageColor = (language) => {
if (typeof colors[language] !== 'undefined') {
return colors[language].color;
} else {
return 'gray';
}
}
export const fallbackImage = (
"data:image/png;base64,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"
)
export const ga = {
// initialize google analytic
initialize: (id) => {
try {
window.gtag('js', new Date());
window.gtag('config', id);
} catch (error) {
console.error(error);
}
},
// log specific events happening
event: ({ action, params }) => {
try {
window.gtag('event', action, params);
} catch (error) {
console.error(error);
}
}
}
export const isThemeDarkish = (theme) => {
if (
theme === 'dark' ||
theme === 'halloween' ||
theme === 'forest' ||
theme === 'black' ||
theme === 'luxury' ||
theme === 'dracula'
) {
return true;
} else {
return false;
}
}
export const setupHotjar = () => {
if (config.hotjar?.id) {
let snippetVersion = config.hotjar?.snippetVersion ? config.hotjar?.snippetVersion : 6;
hotjar.initialize(config.hotjar.id, snippetVersion);
}
}

332
src/helpers/utils.jsx Normal file
View File

@@ -0,0 +1,332 @@
import colors from '../data/colors.json';
import { hotjar } from 'react-hotjar';
export const getInitialTheme = (themeConfig) => {
if (themeConfig.disableSwitch) {
return themeConfig.defaultTheme;
}
if (
typeof window !== 'undefined' &&
!(localStorage.getItem('gitprofile-theme') === null) &&
themeConfig.themes.includes(localStorage.getItem('gitprofile-theme'))
) {
let theme = localStorage.getItem('gitprofile-theme');
return theme;
}
if (themeConfig.respectPrefersColorScheme && !themeConfig.disableSwitch) {
return typeof window !== 'undefined' &&
window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: themeConfig.defaultTheme;
}
return themeConfig.defaultTheme;
};
export const skeleton = ({
width = null,
height = null,
style = {},
shape = 'rounded-full',
className = null,
}) => {
return (
<div
className={`bg-base-300 animate-pulse ${shape}${
className ? ` ${className}` : ''
}${width ? ` ${width}` : ''}${height ? ` ${height}` : ''}`}
style={style}
/>
);
};
export const languageColor = (language) => {
if (typeof colors[language] !== 'undefined') {
return colors[language].color;
} else {
return 'gray';
}
};
export const fallbackImage =
'data:image/png;base64,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';
export const ga = {
// initialize google analytic
initialize: (id) => {
try {
if (typeof window !== 'undefined') {
window.gtag('js', new Date());
window.gtag('config', id);
}
} catch (error) {
console.error(error);
}
},
// log specific events happening
event: ({ action, params }) => {
try {
typeof window !== 'undefined' && window.gtag('event', action, params);
} catch (error) {
console.error(error);
}
},
};
export const isThemeDarkish = (theme) => {
if (
theme === 'dark' ||
theme === 'halloween' ||
theme === 'forest' ||
theme === 'black' ||
theme === 'luxury' ||
theme === 'dracula'
) {
return true;
} else {
return false;
}
};
export const setupHotjar = (hotjarConfig) => {
if (hotjarConfig?.id) {
let snippetVersion = hotjarConfig?.snippetVersion
? hotjarConfig?.snippetVersion
: 6;
hotjar.initialize(hotjarConfig.id, snippetVersion);
}
};
export const sanitizeConfig = (config) => {
const customTheme =
typeof config.themeConfig !== 'undefined' &&
typeof config.themeConfig.customTheme !== 'undefined'
? config.themeConfig.customTheme
: {
primary: '#fc055b',
secondary: '#219aaf',
accent: '#e8d03a',
neutral: '#2A2730',
'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
: [],
},
},
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
: '',
dev:
typeof config.social !== 'undefined' &&
typeof config.social.dev !== 'undefined'
? config.social.dev
: '',
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
: 5,
},
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: (
<p>
Oh no, you hit the{' '}
<a
href="https://developer.github.com/v3/rate_limit/"
target="_blank"
rel="noopener noreferrer"
>
rate limit.
</a>
! Try again later{` ${reset}`}.
</p>
),
};
};
export const notFoundError = {
status: 404,
title: 'The Github Username is Incorrect.',
subTitle: (
<p>
Please provide correct github username in <code>config</code>.
</p>
),
};
export const genericError = {
status: 500,
title: 'Ops!!',
subTitle: 'Something went wrong.',
};

View File

@@ -1,87 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
* {
scrollbar-width: thin;
}
::-webkit-scrollbar-track {
box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
-moz-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
}
@media screen and (min-width: 966px) {
::-webkit-scrollbar, .scroller {
width: 8px;
height: 8px;
background-color: #f1f1f1;
}
}
::-webkit-scrollbar-thumb {
background-color: #888;
border-radius: 10px;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
.text-base-content-important {
color: hsla(var(--bc) / var(--tw-text-opacity)) !important;
}
svg {
vertical-align: unset
}
.z-hover {
transition: all ease-in-out 0.3s !important;
}
.z-hover:hover,
.z-hover:focus,
.z-hover:active {
transition: transform 0.3s !important;
-ms-transform: scale(1.01) !important;
-webkit-transform: scale(1.01) !important;
transform: scale(1.01) !important;
}
.pb-0-important {
padding-bottom: 0 !important;
}
.fade-in {
opacity: 1;
animation-name: fadeIn;
animation-iteration-count: 1;
animation-timing-function: ease-in;
animation-duration: 1s;
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@-webkit-keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}

View File

@@ -1,25 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { HelmetProvider } from 'react-helmet-async';
import { ThemeProvider } from './contexts/ThemeContext';
import { LoadingProvider } from './contexts/LoadingContext';
import { setupHotjar } from './helpers/utils';
ReactDOM.render(
<React.StrictMode>
<ThemeProvider>
<LoadingProvider>
<HelmetProvider>
<App/>
</HelmetProvider>
</LoadingProvider>
</ThemeProvider>
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
setupHotjar();

9
src/main.jsx Normal file
View File

@@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@@ -1,13 +0,0 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

View File

@@ -1,5 +0,0 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

View File

@@ -1,16 +1,16 @@
module.exports = {
purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [
require('daisyui')
import config from './gitprofile.config';
export default {
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [require('daisyui')],
daisyui: {
logs: false,
themes: [
...config.themeConfig.themes,
{ procyon: config.themeConfig.customTheme },
],
daisyui: {
logs: false
},
}
},
};

265
types/index.d.ts vendored Normal file
View File

@@ -0,0 +1,265 @@
// Type definitions for GitProfile
// Project https://github.com/arifszn/gitprofile
// Author: Ariful Alam <arifulalamszn@gmail.com>
import { Component } from 'react';
export interface Github {
/**
* GitHub org/user name
*/
username: string;
/**
* stars | updated
*/
sortBy?: string;
/**
* How many projects to display
*/
limit?: number;
/**
* Exclude projects option
*/
exclude?: {
/**
* Forked projects will not be displayed if set to true
*/
forks?: boolean;
/**
* These projects will not be displayed
*
* example: ['my-project1', 'my-project2']
*/
projects?: Array<string>;
};
}
export interface Social {
/**
* LinkedIn
*/
linkedin?: string;
/**
* Twitter
*/
twitter?: string;
/**
* Facebook
*/
facebook?: string;
/**
* Dribbble
*/
dribbble?: string;
/**
* Behance
*/
behance?: string;
/**
* Medium
*/
medium?: string;
/**
* dev
*/
dev?: string;
/**
* Website
*/
website?: string;
/**
* Phone
*/
phone?: string;
/**
* Email
*/
email?: string;
}
export interface Blog {
/**
* medium | dev
*/
source?: string;
/**
* Username
*/
username?: string;
/**
* How many posts to display
*
* Max is 10
*/
limit?: number;
}
export interface GoogleAnalytics {
/**
* GA3 tracking id/GA4 tag id UA-XXXXXXXXX-X | G-XXXXXXXXXX
*/
id?: string;
}
export interface Hotjar {
/**
* Hotjar id
*/
id?: string;
/**
* Snippet Version
*/
snippetVersion?: number;
}
export interface CustomTheme {
/**
* Primary color
*/
primary?: string;
/**
* Secondary color
*/
secondary?: string;
/**
* Accent color
*/
accent?: string;
/**
* Neutral color
*/
neutral?: string;
/**
* Base color of page
*/
'base-100'?: string;
/**
* Border radius of rounded-box
*/
'--rounded-box'?: string;
/**
* Border radius of rounded-btn
*/
'--rounded-btn'?: string;
}
export interface ThemeConfig {
/**
* Default theme
*/
defaultTheme?: string;
/**
* Hides the switch in the navbar
*/
disableSwitch?: boolean;
/**
* Should use the prefers-color-scheme media-query
*/
respectPrefersColorScheme?: boolean;
/**
* Available themes
*/
themes?: Array<string>;
/**
* Custom theme
*/
customTheme?: CustomTheme;
}
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 {
/**
* GitHub Config
*/
github: Github;
/**
* Social links
*/
social?: Social;
/**
* Skill list
*/
skills?: Array<string>;
/**
* Experience list
*/
experiences?: Array<Experience>;
/**
* Education list
*/
education?: Array<Education>;
/**
* Blog config
*/
blog?: Blog;
/**
* Google Analytics config
*/
googleAnalytics?: GoogleAnalytics;
/**
* Hotjar config
*/
hotjar?: Hotjar;
/**
* Theme config
*/
themeConfig?: ThemeConfig;
}
export interface GitProfileProps {
/**
* Config values
*/
config: Config;
}
declare class GitProfile extends Component<GitProfileProps> {}
export default GitProfile;

18
vite.config.js Normal file
View File

@@ -0,0 +1,18 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwind from 'tailwindcss';
import autoprefixer from 'autoprefixer';
import tailwindConfig from './tailwind.config.js';
// https://vitejs.dev/config/
export default defineConfig({
// If you are deploying to https://<USERNAME>.github.io/, set base to '/'.
// If you are deploying to https://<USERNAME>.github.io/<REPO>/, for example your repository is at https://github.com/<USERNAME>/<REPO>, then set base to '/<REPO>/'.
base: '/gitprofile/',
plugins: [react()],
css: {
postcss: {
plugins: [tailwind(tailwindConfig), autoprefixer],
},
},
});