Compare commits
775 Commits
v1.1.0
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f06dd0f83 | ||
|
|
d73902c9ad | ||
|
|
0ec8198cb6 | ||
|
|
4a5e9a8928 | ||
|
|
30efe53c2f | ||
|
|
16a9ab0296 | ||
|
|
911af25776 | ||
|
|
71cfb3bc3d | ||
|
|
c25f3e716a | ||
|
|
69f6e0f908 | ||
|
|
b0248e1080 | ||
|
|
d2d606650c | ||
|
|
3b7b0cd1a8 | ||
|
|
947abd1b8e | ||
|
|
1d2c043008 | ||
|
|
f6a3c8457d | ||
|
|
521f9deb55 | ||
|
|
d87be16c26 | ||
|
|
eed84cf977 | ||
|
|
7904a16bd2 | ||
|
|
1286062a2b | ||
|
|
f6750b939b | ||
|
|
cac7ee3a16 | ||
|
|
c7db8ee0b8 | ||
|
|
034dbe0d2c | ||
|
|
c5318da457 | ||
|
|
aa4c6928d6 | ||
|
|
fdf75dabeb | ||
|
|
34130dd2a1 | ||
|
|
d2489d1c6f | ||
|
|
93a55a4c32 | ||
|
|
ac61808161 | ||
|
|
f707b702c2 | ||
|
|
cdd8362156 | ||
|
|
d3c3881782 | ||
|
|
f47963ad43 | ||
|
|
e3200cd3b7 | ||
|
|
0838994060 | ||
|
|
f71f205054 | ||
|
|
3dd80aeb26 | ||
|
|
6750afe52f | ||
|
|
3dae9bb7ec | ||
|
|
1608c0ff7b | ||
|
|
838ae6a5b9 | ||
|
|
90b94304d7 | ||
|
|
12a367c25f | ||
|
|
a4f63fbc98 | ||
|
|
9813f0853f | ||
|
|
8d7fc38f45 | ||
|
|
731301d857 | ||
|
|
4ebf2b9742 | ||
|
|
cb0ff53588 | ||
|
|
d14c8fb1a7 | ||
|
|
3cbba324b1 | ||
|
|
7e714b6a8d | ||
|
|
f482c2e7b6 | ||
|
|
205a547439 | ||
|
|
ae5345b33b | ||
|
|
d5317bf0c7 | ||
|
|
31bf2f27d2 | ||
|
|
30f8d05014 | ||
|
|
365f3f25ea | ||
|
|
9abe133b1c | ||
|
|
ac8e6941ab | ||
|
|
b4bb68ae06 | ||
|
|
1bd77a785f | ||
|
|
9ae2d382ad | ||
|
|
06a428a96f | ||
|
|
8c2224047e | ||
|
|
d0f67b6c29 | ||
|
|
788640358f | ||
|
|
7ceb0a12e9 | ||
|
|
e58fb509b0 | ||
|
|
3dc1b74042 | ||
|
|
92115ca1b6 | ||
|
|
a873ab7a8f | ||
|
|
8b798f8dea | ||
|
|
a91de444a2 | ||
|
|
98d550de69 | ||
|
|
80954fce9f | ||
|
|
2a4a10d032 | ||
|
|
80a7f5fd1c | ||
|
|
0f36c6f33c | ||
|
|
f137ffd6fa | ||
|
|
0fd4107ce6 | ||
|
|
639d346cb9 | ||
|
|
054b94983c | ||
|
|
27d763d3f9 | ||
|
|
f9ce8bd292 | ||
|
|
8705fa0a65 | ||
|
|
9274119f87 | ||
|
|
2285d21799 | ||
|
|
18a5096d94 | ||
|
|
cb1359c2af | ||
|
|
8e93cf38a7 | ||
|
|
a1e3e94057 | ||
|
|
cb7f37e054 | ||
|
|
f0ad3c95c2 | ||
|
|
ffdf1a7175 | ||
|
|
4f9ba8df17 | ||
|
|
0a7cbd0985 | ||
|
|
6ce0feef7e | ||
|
|
5de48c11fd | ||
|
|
5c766305ea | ||
|
|
815f2ec64e | ||
|
|
ceb84a1562 | ||
|
|
4b9dfb3c20 | ||
|
|
46c84582e9 | ||
|
|
b3907304bf | ||
|
|
7f28179920 | ||
|
|
a65fb9bfd0 | ||
|
|
24892e5f27 | ||
|
|
800f61e0ec | ||
|
|
c96ae81e13 | ||
|
|
58aad33f32 | ||
|
|
81fe01c092 | ||
|
|
87126da8c7 | ||
|
|
37e1fc11ec | ||
|
|
ef87189024 | ||
|
|
90f38bec5f | ||
|
|
ce2711fbdf | ||
|
|
35edab65db | ||
|
|
7c601dbea3 | ||
|
|
4f74eed832 | ||
|
|
fecce68262 | ||
|
|
265c42cbc3 | ||
|
|
634f5f4d51 | ||
|
|
cccb649cd2 | ||
|
|
9ce0d8d3f3 | ||
|
|
f481d9c25d | ||
|
|
2cd12b5e82 | ||
|
|
09edcddf1e | ||
|
|
937c762cf4 | ||
|
|
ceaa281c34 | ||
|
|
1d6001a065 | ||
|
|
506c3c47bc | ||
|
|
76f3f5eedd | ||
|
|
70815d0f18 | ||
|
|
171407b98f | ||
|
|
4893129df0 | ||
|
|
cb99de5994 | ||
|
|
99891187c4 | ||
|
|
4ad60a28fc | ||
|
|
97bfff7d67 | ||
|
|
6bc2028f28 | ||
|
|
8b9e6697eb | ||
|
|
12eb053631 | ||
|
|
c82002bbcf | ||
|
|
b9b71301a6 | ||
|
|
4eb7f0a376 | ||
|
|
6b3e56f57b | ||
|
|
70845a7e56 | ||
|
|
2f1e506d69 | ||
|
|
417bbb1ab3 | ||
|
|
0bf13d0443 | ||
|
|
ea157d40be | ||
|
|
7d0d750bdb | ||
|
|
a2335c49ea | ||
|
|
d689ea8157 | ||
|
|
81c968e01d | ||
|
|
f9218b343d | ||
|
|
8e6d0d8b56 | ||
|
|
5db163a98e | ||
|
|
eb545d13f0 | ||
|
|
4979fbefc6 | ||
|
|
a209f1bed2 | ||
|
|
d6c316abd9 | ||
|
|
3e00c068c3 | ||
|
|
5ddf1ed649 | ||
|
|
4f063ade73 | ||
|
|
fc3f58c0db | ||
|
|
19fb3e1e64 | ||
|
|
ffe04ecf33 | ||
|
|
521efd3d07 | ||
|
|
7a53d4aea4 | ||
|
|
98f44262a9 | ||
|
|
0c1c378055 | ||
|
|
58eb3a841d | ||
|
|
b7ca57e6a9 | ||
|
|
20e93593af | ||
|
|
157b3f85a5 | ||
|
|
a7d342601b | ||
|
|
cf0a344550 | ||
|
|
268f70d53e | ||
|
|
bfa599a508 | ||
|
|
83bfb85b4f | ||
|
|
86b313d143 | ||
|
|
d6e6213991 | ||
|
|
1a0af42ba6 | ||
|
|
30cd7cb152 | ||
|
|
b3de46ba51 | ||
|
|
4ee69ffd98 | ||
|
|
22d6ee0d0f | ||
|
|
f5b9ed4afb | ||
|
|
76fa0740e6 | ||
|
|
5803c2149c | ||
|
|
78413294ea | ||
|
|
5631516710 | ||
|
|
13a715a1a5 | ||
|
|
ca63dc0b5d | ||
|
|
f90076df3f | ||
|
|
30ea06d78e | ||
|
|
17156c1dad | ||
|
|
f0c3098ad6 | ||
|
|
efaebb7892 | ||
|
|
1b4a87b16f | ||
|
|
41279736f5 | ||
|
|
d31d14ddd9 | ||
|
|
85a515638a | ||
|
|
5fb622858e | ||
|
|
9ebb6debc4 | ||
|
|
b06ddf911a | ||
|
|
2c9700d47e | ||
|
|
1229633a77 | ||
|
|
da5e690de0 | ||
|
|
e1ee10a39c | ||
|
|
e7a343c1c0 | ||
|
|
e643996019 | ||
|
|
f38bbafb2d | ||
|
|
de4b91f282 | ||
|
|
fec425195d | ||
|
|
a15c278f08 | ||
|
|
9f32665a7a | ||
|
|
62ae9293d4 | ||
|
|
c964a40478 | ||
|
|
e2f0e76f7a | ||
|
|
52907c78ff | ||
|
|
d16f45702d | ||
|
|
9258c1de46 | ||
|
|
28bdd6c3fe | ||
|
|
444e220657 | ||
|
|
a1525e87b2 | ||
|
|
f141ef4a1d | ||
|
|
8373d93ba5 | ||
|
|
e56dc34e1e | ||
|
|
027d427db9 | ||
|
|
394c7f2476 | ||
|
|
80d93e976a | ||
|
|
7fd43fac48 | ||
|
|
2ff2e301e9 | ||
|
|
0515e2ec62 | ||
|
|
8853f2f2b5 | ||
|
|
c888fb77a5 | ||
|
|
5a30b610b3 | ||
|
|
46b228615a | ||
|
|
07d4595738 | ||
|
|
1226b4436b | ||
|
|
101882f503 | ||
|
|
d7e05033c5 | ||
|
|
f1c07e98cb | ||
|
|
3a10225e81 | ||
|
|
ad6e7aaddb | ||
|
|
f15703f68c | ||
|
|
2db8bf69e6 | ||
|
|
223a2de0a3 | ||
|
|
65b427481c | ||
|
|
302071014e | ||
|
|
59151b02f4 | ||
|
|
7220ff4c92 | ||
|
|
0a89bc8233 | ||
|
|
02ed553904 | ||
|
|
5575e6c5f2 | ||
|
|
03ee76d815 | ||
|
|
15dce3a539 | ||
|
|
38a7adffc8 | ||
|
|
82ce615ff6 | ||
|
|
8d85ac349c | ||
|
|
c7ac31bb99 | ||
|
|
d307af49ac | ||
|
|
42dd2303be | ||
|
|
8fbbc0e0c5 | ||
|
|
2364f267cf | ||
|
|
fff850f94f | ||
|
|
382e3835b2 | ||
|
|
8988fde115 | ||
|
|
203273f349 | ||
|
|
bf0404222d | ||
|
|
62fc6941ed | ||
|
|
01f6a21083 | ||
|
|
45fd66e595 | ||
|
|
4eeb320f92 | ||
|
|
9cd95eb08d | ||
|
|
32740ede32 | ||
|
|
984bee1fcf | ||
|
|
53b4a1ee39 | ||
|
|
7ec5802b54 | ||
|
|
71223e4c04 | ||
|
|
04603e7bd3 | ||
|
|
422e0ba12f | ||
|
|
9382fcd1cd | ||
|
|
dab07bebf7 | ||
|
|
4ecbe62332 | ||
|
|
37587c5e7a | ||
|
|
cdcd39beaf | ||
|
|
7c94acfc53 | ||
|
|
dd01541a98 | ||
|
|
3cf6c15103 | ||
|
|
c91974514c | ||
|
|
02a568bf90 | ||
|
|
ecf1b42136 | ||
|
|
a7fa79b3ee | ||
|
|
939a4b789b | ||
|
|
5497a6f095 | ||
|
|
307d0cbc4c | ||
|
|
dd540ed60d | ||
|
|
50eef86608 | ||
|
|
4c2c40bbbd | ||
|
|
bb0eae5d7a | ||
|
|
0beb2c6adc | ||
|
|
16a8fccdd9 | ||
|
|
51655eecf1 | ||
|
|
cf39d43367 | ||
|
|
87c18a2d1f | ||
|
|
d35e145106 | ||
|
|
6891901bc4 | ||
|
|
cb7c233787 | ||
|
|
f3fdb85d7c | ||
|
|
d05e70788d | ||
|
|
f1b5243926 | ||
|
|
2b619792de | ||
|
|
89c85fe4e8 | ||
|
|
ae8cc57533 | ||
|
|
fcbc08d483 | ||
|
|
d1b2404e97 | ||
|
|
b236d19940 | ||
|
|
26f4d47b17 | ||
|
|
9cc38ca819 | ||
|
|
f8bae71a00 | ||
|
|
c07cd59c06 | ||
|
|
d1bd06f05f | ||
|
|
078b79eb95 | ||
|
|
0c9051bf64 | ||
|
|
7ad35bb66b | ||
|
|
ba45cabee5 | ||
|
|
11f54ff5c7 | ||
|
|
d70f5408b3 | ||
|
|
49bd7c1108 | ||
|
|
c519cbc0c9 | ||
|
|
79a9e0704a | ||
|
|
6805d6eac1 | ||
|
|
dda3e18544 | ||
|
|
3cbea96846 | ||
|
|
6854a7b80c | ||
|
|
438ba16020 | ||
|
|
91c3a36f1e | ||
|
|
23732a7c16 | ||
|
|
697ca9f179 | ||
|
|
6faf857173 | ||
|
|
dd34d0155c | ||
|
|
ed2e832599 | ||
|
|
8d4462c700 | ||
|
|
2619676c6e | ||
|
|
194503f386 | ||
|
|
57933cceb8 | ||
|
|
1baf8bd460 | ||
|
|
4edb171236 | ||
|
|
9e34239877 | ||
|
|
9d8a5ab51b | ||
|
|
e895b2e8f3 | ||
|
|
cdb3542844 | ||
|
|
a8abc23735 | ||
|
|
fe6974d58e | ||
|
|
939ecb6112 | ||
|
|
8498ad8ada | ||
|
|
b52c9b80a4 | ||
|
|
5ca5cff5d2 | ||
|
|
c06a0929be | ||
|
|
18afaccd24 | ||
|
|
89e5c29d80 | ||
|
|
20cc39d7ce | ||
|
|
e4f06638f9 | ||
|
|
7349310873 | ||
|
|
dc3724eb2c | ||
|
|
b69f052c54 | ||
|
|
c9b2c46495 | ||
|
|
ab035d13e5 | ||
|
|
17ff018f87 | ||
|
|
17abae3915 | ||
|
|
b3ab995397 | ||
|
|
5d3eacb694 | ||
|
|
6f1c0db559 | ||
|
|
1c320a4e82 | ||
|
|
de44eb7a48 | ||
|
|
af9b0ea531 | ||
|
|
c64cd57a95 | ||
|
|
76de845b46 | ||
|
|
c03487cb5b | ||
|
|
f30a588abc | ||
|
|
569e73859d | ||
|
|
caf2d55af8 | ||
|
|
36d8c0ac01 | ||
|
|
b23e4956e6 | ||
|
|
6e0dac1050 | ||
|
|
98678e7a1c | ||
|
|
ffa7f99109 | ||
|
|
c81123c804 | ||
|
|
b13209b956 | ||
|
|
afa89fca4f | ||
|
|
f013b314ff | ||
|
|
fc10df86bf | ||
|
|
84a109a31f | ||
|
|
486faffb0b | ||
|
|
110286622d | ||
|
|
5fceae602d | ||
|
|
e42f4a7b08 | ||
|
|
6927c5d425 | ||
|
|
c9fce00077 | ||
|
|
4430d982f4 | ||
|
|
39a97afb22 | ||
|
|
6b638f03ea | ||
|
|
3e0af6dad2 | ||
|
|
83a254e687 | ||
|
|
b42202d923 | ||
|
|
e6f678eacd | ||
|
|
b6bccf904e | ||
|
|
a7d7195487 | ||
|
|
a60fac9a95 | ||
|
|
a603e4236d | ||
|
|
1dec29a718 | ||
|
|
3f1b0ba513 | ||
|
|
0a0cd6bae1 | ||
|
|
9605a47a2a | ||
|
|
29a75d06cd | ||
|
|
e20a1d7321 | ||
|
|
38bc0d0025 | ||
|
|
0d44af1148 | ||
|
|
75e148e897 | ||
|
|
62062ae956 | ||
|
|
97cdde0abd | ||
|
|
f1b3fbda47 | ||
|
|
9c0bcdd973 | ||
|
|
2cac4c5994 | ||
|
|
27beccb1ee | ||
|
|
05a807387f | ||
|
|
cccd7afce4 | ||
|
|
020129b523 | ||
|
|
e0c242b523 | ||
|
|
05416767f8 | ||
|
|
12b6091d34 | ||
|
|
c2167f870a | ||
|
|
fb7ffe269e | ||
|
|
de448dcc3f | ||
|
|
b803ef88d0 | ||
|
|
d8450294ff | ||
|
|
d7c1b574a3 | ||
|
|
88bed3b701 | ||
|
|
d2d8d6fe43 | ||
|
|
2e0603d52b | ||
|
|
e725ef9b5c | ||
|
|
5dc71954a4 | ||
|
|
f25f5bf2d5 | ||
|
|
c375ec521f | ||
|
|
67c881eb05 | ||
|
|
21eaa93a72 | ||
|
|
69d636e3bb | ||
|
|
81ec352c4a | ||
|
|
8752d18408 | ||
|
|
2e6c69cdbc | ||
|
|
3d170a9a31 | ||
|
|
feba26f3aa | ||
|
|
a14318f776 | ||
|
|
247445b63c | ||
|
|
5628f76449 | ||
|
|
742df933a7 | ||
|
|
e11be358f9 | ||
|
|
771f1fe0d4 | ||
|
|
5b448adc76 | ||
|
|
ea424fe9e4 | ||
|
|
50ed6b90ba | ||
|
|
b96298e702 | ||
|
|
c23cbc6c8e | ||
|
|
1ec9274f4d | ||
|
|
f97d1544d5 | ||
|
|
970044b7f1 | ||
|
|
a65bbf2937 | ||
|
|
ebdc7d533d | ||
|
|
5fe64aecb3 | ||
|
|
ec3cdf4f68 | ||
|
|
80ff09b295 | ||
|
|
ca57b1bfa2 | ||
|
|
4abc13ad71 | ||
|
|
96cc63e911 | ||
|
|
145985fb54 | ||
|
|
023422d318 | ||
|
|
73da16ed19 | ||
|
|
09a0eebb16 | ||
|
|
2c1219bcda | ||
|
|
57b6a90fab | ||
|
|
fb3852b3c7 | ||
|
|
49509840aa | ||
|
|
3385e4f260 | ||
|
|
e967b89ca1 | ||
|
|
b2106dd441 | ||
|
|
9bf3d3b245 | ||
|
|
e1a1d1a0bf | ||
|
|
73f38a3f0e | ||
|
|
f36ab50a30 | ||
|
|
cd557c0e52 | ||
|
|
1713b6d82b | ||
|
|
92a49a6302 | ||
|
|
7f9d0d9514 | ||
|
|
692c8ba8c7 | ||
|
|
ac7356a222 | ||
|
|
fe5fec779e | ||
|
|
eb4d65b44a | ||
|
|
b6c7a2ede0 | ||
|
|
91d4e799a4 | ||
|
|
e724ca8aca | ||
|
|
d87f8986ee | ||
|
|
0f9ceb0d98 | ||
|
|
b2429b359d | ||
|
|
cacd093f6f | ||
|
|
afc303a889 | ||
|
|
4be3b67118 | ||
|
|
143a16c410 | ||
|
|
dc8f1d68a9 | ||
|
|
68d04646b3 | ||
|
|
03e267fe99 | ||
|
|
1cc331be52 | ||
|
|
fee3cebf54 | ||
|
|
903af97cfe | ||
|
|
b7c8b21999 | ||
|
|
6fce517b0b | ||
|
|
dbdabc7b4a | ||
|
|
66bd370f34 | ||
|
|
caff2ce258 | ||
|
|
a4ad42f40d | ||
|
|
4edef5df4b | ||
|
|
9cab9f209b | ||
|
|
f4a1fff90f | ||
|
|
ac00471cdc | ||
|
|
d97c758629 | ||
|
|
098e5cdd1e | ||
|
|
8d4d8b219e | ||
|
|
0805080cce | ||
|
|
2c60ff1ec9 | ||
|
|
f115c763a2 | ||
|
|
e5aa0d516e | ||
|
|
1ae972acd3 | ||
|
|
ded02858a8 | ||
|
|
fb7b64c4e8 | ||
|
|
8901157e01 | ||
|
|
6944b2ca4d | ||
|
|
a8a56d67e4 | ||
|
|
04e14af71a | ||
|
|
a4b296172e | ||
|
|
e16f50b349 | ||
|
|
0c2b93ff85 | ||
|
|
fb55338ea9 | ||
|
|
c279dc9ac5 | ||
|
|
6ef1a5fe9b | ||
|
|
eccb38a29c | ||
|
|
107ffae90d | ||
|
|
56b5906e01 | ||
|
|
ed6396c2c2 | ||
|
|
f41fc775a9 | ||
|
|
5e3fa7075a | ||
|
|
21634afb62 | ||
|
|
5fcb803436 | ||
|
|
305044a8fc | ||
|
|
dc80a9db9e | ||
|
|
e8d0d8eb28 | ||
|
|
efbd5f96fe | ||
|
|
630d9711ec | ||
|
|
647195f224 | ||
|
|
be09c170b9 | ||
|
|
6020ecf818 | ||
|
|
7cd1bd87fd | ||
|
|
0d6eef906f | ||
|
|
6b3d089b4b | ||
|
|
36097918fd | ||
|
|
e5cc04ec57 | ||
|
|
b1fcdcf0c5 | ||
|
|
2b50dfcf6c | ||
|
|
43b0bc91f0 | ||
|
|
6672d91d1c | ||
|
|
aad79fa521 | ||
|
|
fb08deafd6 | ||
|
|
7f73463863 | ||
|
|
5983a95a9f | ||
|
|
ea9f8977b8 | ||
|
|
4b07170613 | ||
|
|
f6f38d908c | ||
|
|
dcd1b56fe7 | ||
|
|
166392a306 | ||
|
|
5a4423e7fa | ||
|
|
4800b0e1ae | ||
|
|
ff3d0f1abd | ||
|
|
035eb95723 | ||
|
|
0f3becb8f9 | ||
|
|
9486c2b3ef | ||
|
|
bbb8911495 | ||
|
|
f0849a1bbb | ||
|
|
bbb7f41dd3 | ||
|
|
d7194ecada | ||
|
|
4b5ec65fe7 | ||
|
|
36f9487eca | ||
|
|
5731e64819 | ||
|
|
66d680e2ec | ||
|
|
6f64574f5c | ||
|
|
28abcd0570 | ||
|
|
aef59d1d68 | ||
|
|
5345d2af3d | ||
|
|
df281242e1 | ||
|
|
fabb4f67a7 | ||
|
|
665ee17be9 | ||
|
|
296c2adc46 | ||
|
|
fb8f5e3bec | ||
|
|
19955a44d0 | ||
|
|
7d9b1d9606 | ||
|
|
48f4920c1d | ||
|
|
8dbe879711 | ||
|
|
cdc66ea70b | ||
|
|
851f761104 | ||
|
|
6c2ca85bc9 | ||
|
|
54ff094b64 | ||
|
|
e46529b25b | ||
|
|
21f8b0633d | ||
|
|
37618822b7 | ||
|
|
d00fa61b08 | ||
|
|
f75eae4547 | ||
|
|
2743dee9aa | ||
|
|
4cb107e168 | ||
|
|
59a9a2fac4 | ||
|
|
263984b0a4 | ||
|
|
7794d543d3 | ||
|
|
2623da910b | ||
|
|
277fe5b01c | ||
|
|
9f24ddbe46 | ||
|
|
8dedc09ea8 | ||
|
|
82eb282e17 | ||
|
|
c6066fd038 | ||
|
|
5058711985 | ||
|
|
bbac82bd16 | ||
|
|
39125b018c | ||
|
|
7964ae57bb | ||
|
|
b3715bca0a | ||
|
|
07e1e090a1 | ||
|
|
c453eb95fd | ||
|
|
9ac2e49a58 | ||
|
|
24b2194129 | ||
|
|
d5bea5a160 | ||
|
|
2209b21942 | ||
|
|
725091dc52 | ||
|
|
4013491663 | ||
|
|
4390701a8c | ||
|
|
ad2439316e | ||
|
|
a4b1660004 | ||
|
|
4bbbf7cde3 | ||
|
|
b89dd276df | ||
|
|
7f007a8bb0 | ||
|
|
741e8ff6cd | ||
|
|
ebc01a51f9 | ||
|
|
f309fdb5a6 | ||
|
|
764fbea04b | ||
|
|
e0b62bb38c | ||
|
|
db3d5c363d | ||
|
|
f409069210 | ||
|
|
da281dfc80 | ||
|
|
a40a6a669f | ||
|
|
d7fa99c787 | ||
|
|
1f74163548 | ||
|
|
ea71fdee1d | ||
|
|
d719e38531 | ||
|
|
f8fff21f3c | ||
|
|
e9ac566737 | ||
|
|
0a9671bddc | ||
|
|
adc5fc857f | ||
|
|
ab3eb39618 | ||
|
|
101e00845d | ||
|
|
f711238d73 | ||
|
|
fe21ead1f2 | ||
|
|
20ac254201 | ||
|
|
b864c2cdd7 | ||
|
|
d1fb373ead | ||
|
|
42746ae8ef | ||
|
|
07d0b15460 | ||
|
|
a6063253f2 | ||
|
|
059dce3dbe | ||
|
|
2ba2674a7f | ||
|
|
19243fde6a | ||
|
|
4ac53af497 | ||
|
|
b5a3cfa798 | ||
|
|
b020acd1bc | ||
|
|
79c10e0e0b | ||
|
|
ec458191fb | ||
|
|
a2ada305a7 | ||
|
|
3354eb9f9b | ||
|
|
b1ba17e17a | ||
|
|
ddcb175234 | ||
|
|
3529f39b7f | ||
|
|
7866762a76 | ||
|
|
189e0f695e | ||
|
|
875ba4c899 | ||
|
|
c1068821a1 | ||
|
|
8f45e648c1 | ||
|
|
7da02cbceb | ||
|
|
5c9f17d5f1 | ||
|
|
3a67c9ba31 | ||
|
|
34e2d1da01 | ||
|
|
e91c8d9df0 | ||
|
|
2951f53fc0 | ||
|
|
da1810adf0 | ||
|
|
c3360fa0f7 | ||
|
|
ab093235e3 | ||
|
|
32c663fd55 | ||
|
|
b4d65da1a7 | ||
|
|
b4c81095e8 | ||
|
|
86ad16ee31 | ||
|
|
fdffd280b7 | ||
|
|
7a935e9cb5 | ||
|
|
206ae1c8b6 | ||
|
|
1565307200 | ||
|
|
ee36b5c49c | ||
|
|
1a87e391b2 | ||
|
|
ab55fa68f1 | ||
|
|
b69ced8ce0 | ||
|
|
2b94b40bf3 | ||
|
|
10238cc3a4 | ||
|
|
d79bec006a | ||
|
|
d76c4fd52d | ||
|
|
6673428533 | ||
|
|
9b1e01b8df | ||
|
|
ce8a736106 | ||
|
|
be3e0d1ffb | ||
|
|
0ff1235735 | ||
|
|
a533524604 | ||
|
|
92bdc1acf6 | ||
|
|
9d1de8173f | ||
|
|
c0d1a68123 | ||
|
|
1fc3ca059a | ||
|
|
fce65c18e2 | ||
|
|
aaf7e4a8cf | ||
|
|
a3db9fddb3 | ||
|
|
d47bbe079c | ||
|
|
87d92c4c91 | ||
|
|
e47a6f722e | ||
|
|
ff83fa6be8 | ||
|
|
100bd7816e | ||
|
|
cf043dab4c | ||
|
|
6979ee8c5c | ||
|
|
6cb49fc61b | ||
|
|
91667f155f | ||
|
|
50f0f49afe | ||
|
|
54b21cfcbc | ||
|
|
18e7e671f3 | ||
|
|
1ff81c8358 | ||
|
|
f01ca017a3 | ||
|
|
528c278890 | ||
|
|
9c324acddf | ||
|
|
7b7502a118 | ||
|
|
6a9496aa51 | ||
|
|
0bc0d0aa9c | ||
|
|
59e53ac245 | ||
|
|
9280f317ee | ||
|
|
893f52ee96 | ||
|
|
529b4047f3 | ||
|
|
5d2fafa0b0 | ||
|
|
2d4376645d | ||
|
|
545212d7f5 | ||
|
|
a0557a1691 | ||
|
|
d19f490d26 | ||
|
|
f4cfcbbe5e | ||
|
|
a1f983ef16 | ||
|
|
d8880234e0 | ||
|
|
654a5449bb | ||
|
|
daa1212e66 | ||
|
|
30399395c1 | ||
|
|
fd6d5a09dd | ||
|
|
23f07cc8ef | ||
|
|
2ded9a0625 | ||
|
|
f5c197e2c5 | ||
|
|
49db2972c2 | ||
|
|
5da67dfcc0 | ||
|
|
2fd66cc027 |
22
.eslintignore
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
19
.eslintrc.cjs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: { browser: true, es2020: true, node: true },
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:react-hooks/recommended',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
],
|
||||||
|
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: ['react-refresh'],
|
||||||
|
rules: {
|
||||||
|
'react-refresh/only-export-components': [
|
||||||
|
'warn',
|
||||||
|
{ allowConstantExport: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
6
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: 'npm'
|
||||||
|
directory: '/'
|
||||||
|
schedule:
|
||||||
|
interval: 'weekly'
|
||||||
39
.github/workflows/CI-CD.yml
vendored
@@ -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
|
|
||||||
61
.github/workflows/deploy.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
name: Deploy to GitHub Pages
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20.x
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Setup Pages
|
||||||
|
uses: actions/configure-pages@v4
|
||||||
|
|
||||||
|
- name: Restore cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
**/node_modules
|
||||||
|
key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build with vite
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
path: ./dist
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
steps:
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
39
.github/workflows/test-deploy.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
name: Test Deployment
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20.x
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Restore cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
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
@@ -1,23 +1,24 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
# Logs
|
||||||
|
logs
|
||||||
# dependencies
|
*.log
|
||||||
/node_modules
|
|
||||||
/.pnp
|
|
||||||
.pnp.js
|
|
||||||
|
|
||||||
# testing
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# production
|
|
||||||
/build
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
|
||||||
.env.local
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
|
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.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?
|
||||||
|
|||||||
22
.prettierignore
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
9
.prettierrc
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"arrowParens": "always",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"printWidth": 80,
|
||||||
|
"singleQuote": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"endOfLine": "auto"
|
||||||
|
}
|
||||||
76
CODE_OF_CONDUCT.md
Normal 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
|
||||||
49
CONTRIBUTING.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
1. Make sure that the contribution you want to make is explained or detailed in a GitHub issue! Find an [existing issue](https://github.com/arifszn/gitprofile/issues) or [open a new one](https://github.com/arifszn/gitprofile/issues/new).
|
||||||
|
2. Once done, [fork the repository](https://github.com/arifszn/gitprofile/fork) in your own GitHub account.
|
||||||
|
3. [Create a new Git branch](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-and-deleting-branches-within-your-repository).
|
||||||
|
4. Make the changes on your branch.
|
||||||
|
5. [Submit the branch as a PR](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork) pointing to the `main` branch of the main repository. <br>
|
||||||
|
We do not enforce a naming convention for the PRs, but **please use something descriptive of your changes**.
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
### Install dependencies
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run dev server
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linter
|
||||||
|
|
||||||
|
Each PR should pass the linter to be accepted. To fix lint and prettier errors, run `npm run lint:fix` and `npm run prettier:fix`.
|
||||||
|
|
||||||
|
### Commit Message
|
||||||
|
|
||||||
|
As minimal requirements, your commit message should:
|
||||||
|
|
||||||
|
- be capitalized
|
||||||
|
- not finish by a dot or any other punctuation character (!,?)
|
||||||
|
- start with a verb so that we can read your commit message this way: "This commit will ...", where "..." is the commit message.
|
||||||
|
e.g.: "Fix the home page button" or "Add support for dark mode"
|
||||||
214
LICENSE
@@ -1,201 +1,21 @@
|
|||||||
Apache License
|
MIT License
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
Copyright (c) 2022 Ariful Alam
|
||||||
|
|
||||||
1. Definitions.
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
The above copyright notice and this permission notice shall be included in all
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
the copyright owner that is granting the License.
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
other entities that control, are controlled by, or are under common
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
control with that entity. For the purposes of this definition,
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
SOFTWARE.
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright 2021 MD. Ariful Alam
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
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.
|
|
||||||
|
|||||||
699
README.md
@@ -1,166 +1,314 @@
|
|||||||
<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>
|
|
||||||
</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>
|
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://arifszn.github.io/ezprofile">
|
<img src="https://user-images.githubusercontent.com/45073703/177566625-9b84e793-4559-4475-ba54-8d3d5f4123d4.png" width="35%">
|
||||||
<img src="https://arifszn.github.io/assets/img/hosted/ezprofile/preview.gif" width="60%" alt="Preview"/>
|
|
||||||
|
<h4 align="center">Easy to use automatic portfolio builder for every GitHub user!</h4>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://codeclimate.com/github/arifszn/gitprofile/maintainability">
|
||||||
|
<img src="https://api.codeclimate.com/v1/badges/c60f42d7d0b61bd33e98/maintainability" />
|
||||||
</a>
|
</a>
|
||||||
<br/>
|
<a href="https://github.com/arifszn/gitprofile/actions/workflows/test-deploy.yml">
|
||||||
<a href="#arifszn"><img src="https://arifszn.github.io/assets/img/drop-shadow.png" width="60%" alt="Shadow"/></a>
|
<img src="https://github.com/arifszn/gitprofile/actions/workflows/test-deploy.yml/badge.svg" />
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/arifszn/gitprofile/issues">
|
||||||
|
<img src="https://img.shields.io/github/issues/arifszn/gitprofile"/>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/arifszn/gitprofile/stargazers">
|
||||||
|
<img src="https://img.shields.io/github/stars/arifszn/gitprofile"/>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/arifszn/gitprofile/network/members">
|
||||||
|
<img src="https://img.shields.io/github/forks/arifszn/gitprofile"/>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/arifszn/gitprofile/commits/main">
|
||||||
|
<img src="https://img.shields.io/github/last-commit/arifszn/gitprofile/main"/>
|
||||||
|
</a>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://arifszn.github.io/gitprofile">View Demo</a>
|
||||||
|
·
|
||||||
|
<a href="https://github.com/arifszn/gitprofile/issues">Report Bug</a>
|
||||||
|
·
|
||||||
|
<a href="https://github.com/arifszn/gitprofile/discussions">Request Feature</a>
|
||||||
|
</p>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
**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.
|
<p align="center">
|
||||||
|
<a href="https://arifszn.github.io/gitprofile">
|
||||||
|
<img src="https://github.com/arifszn/gitprofile/assets/45073703/eb6c38a4-ac92-4006-869b-e4e24f6f5cf6" alt="Preview" width="60%"/>
|
||||||
|
</a>
|
||||||
|
<br/>
|
||||||
|
<a href="#arifszn"><img src="https://github.com/arifszn/gitprofile/assets/45073703/4d2ccd45-e566-4743-bf61-cadc03ece54c" width="50%" alt="Shadow"/></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
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).
|
**GitProfile** is a powerful portfolio builder that allows you to create a stunning and personalized portfolio site in minutes, even if you have no coding experience. Simply provide your GitHub username, and GitProfile will automatically generate a portfolio. Best of all, you can easily deploy your portfolio to GitHub Pages with just a few clicks, making it accessible to the world in no time.
|
||||||
|
|
||||||
✓ [21 Themes](#themes)\
|
**Features:**
|
||||||
✓ [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)
|
|
||||||
|
|
||||||
To view a live example, **[click here](https://arifszn.github.io/ezprofile)**.
|
✓ [Easy to Setup](#-installation--setup)
|
||||||
|
✓ [33 Themes](#themes)
|
||||||
|
✓ [Google Analytics](#google-analytics)
|
||||||
|
✓ [Hotjar](#hotjar)
|
||||||
|
✓ [SEO](#seo)
|
||||||
|
✓ [PWA](#pwa)
|
||||||
|
✓ [Avatar and Bio](#avatar-and-bio)
|
||||||
|
✓ [Social Links](#social-links)
|
||||||
|
✓ [Skill Section](#skills)
|
||||||
|
✓ [Experience Section](#experience)
|
||||||
|
✓ [Certification Section](#certifications)
|
||||||
|
✓ [Education Section](#education)
|
||||||
|
✓ [Projects Section](#projects)
|
||||||
|
✓ [Blog Posts Section](#blog-posts)
|
||||||
|
|
||||||
|
To view a live example, **[click here](https://arifszn.github.io/gitprofile)**.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://github.com/arifszn/gitprofile/assets/45073703/406e8368-415a-42ef-89c5-d43cc8bbeb19" alt="Themes">
|
||||||
|
</p>
|
||||||
|
|
||||||
## 🛠 Installation & Set Up
|
## 🛠 Installation & Setup
|
||||||
|
|
||||||
These instructions will get you a copy of the project and deploy your website online!
|
There are three ways to use **GitProfile**. Use any.
|
||||||
|
|
||||||
- **[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.
|
- [Forking this repo _(recommended)_](#forking-this-repo)
|
||||||
- Rename your forked repository to <code>username.github.io</code> in github, where <code>username</code> is your GitHub username (or organization name).
|
- [Setting up locally](#setting-up-locally)
|
||||||
- Go to your repo's **Actions** page and enable workflows.\
|
|
||||||

|
|
||||||
|
|
||||||
- Open <code>package.json</code>, and change <code>homepage</code>'s value to <code>https://username.github.io</code>.
|
### Forking this repo
|
||||||
|
|
||||||
```js
|
These instructions will get you a copy of the project and deploy your portfolio online using GitHub Pages!
|
||||||
// package.json
|
|
||||||
|
- **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:**
|
||||||
|
- If you want to host your portfolio at `https://<USERNAME>.github.io`, rename your forked repository to `username.github.io` in GitHub, where `username` is your GitHub username (or organization name).
|
||||||
|
- If you want to host your portfolio at `https://<USERNAME>.github.io/<REPO_NAME>` (e.g. `https://<USERNAME>.github.io/portfolio`), rename your forked repository to `<REPO_NAME>` (e.g. `portfolio`) in GitHub.
|
||||||
|
- **Enable workflows:** Go to your repo's **Actions** tab and enable workflows.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **Base Value:** Open `gitprofile.config.ts`, and change `base`'s value.
|
||||||
|
|
||||||
|
- If you are deploying to `https://<USERNAME>.github.io`, set `base` to `'/'`.
|
||||||
|
|
||||||
|
- If you are deploying to `https://<USERNAME>.github.io/<REPO_NAME>` (e.g. `https://<USERNAME>.github.io/portfolio`), then set `base` to `'/<REPO_NAME>/'` (e.g. `'/portfolio/'`).
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// gitprofile.config.ts
|
||||||
{
|
{
|
||||||
|
base: '/',
|
||||||
// ...
|
// ...
|
||||||
"homepage": "https://username.github.io",
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- Now commit to your **main** branch with your changes.
|
- **Commit the changes:** Now commit to your **main** branch with your changes. Wait a few minutes so that the CI/CD pipeline can publish your website to GitHub Pages. You can check the progress in the [Actions](https://github.com/arifszn/gitprofile/actions) tab.
|
||||||
- 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 automatically update.
|
|
||||||
|
|
||||||
|
Your portfolio website will be live shortly. Any time you commit a change to the **main** branch, the website will be automatically updated. If you face any issue viewing the website, double-check the `base` value in the `gitprofile.config.ts` file. Also, check if **Source** is set to **GitHub Actions** in **Settings** ➜ **Pages** ➜ **Build and deployment**.
|
||||||
|
|
||||||
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).
|
If you wish to add a custom domain, no CNAME file is required. Just add it to your repo's **Settings** ➜ **Pages** ➜ **Custom domain**.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
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).
|
### Setting up locally
|
||||||
|
|
||||||
|
- Clone the project and change directory.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
git clone https://github.com/arifszn/gitprofile.git
|
||||||
|
cd gitprofile
|
||||||
|
```
|
||||||
|
|
||||||
|
- Install dependencies.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
- Run dev server.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
- Finally, visit [`http://localhost:5173/gitprofile/`](http://localhost:5173/gitprofile/) from your browser.
|
||||||
|
|
||||||
|
> Alternatively, you can set up and run the project using Docker with **[`vail`](https://github.com/arifszn/vail)**, a powerful tool for local development of JavaScript/TypeScript Apps.
|
||||||
|
|
||||||
## 🎨 Customization
|
## 🎨 Customization
|
||||||
|
|
||||||
All the magic happens in the file <code>src/config.js</code>. Open it and modify it according to your preference.
|
All the magic happens in the file `gitprofile.config.ts`. Open it and modify it according to your preference.
|
||||||
|
|
||||||
These are the default values:
|
```ts
|
||||||
|
// gitprofile.config.ts
|
||||||
|
|
||||||
<details>
|
const CONFIG = {
|
||||||
<summary>config.js</summary>
|
|
||||||
|
|
||||||
```js
|
|
||||||
// config.js
|
|
||||||
module.exports = {
|
|
||||||
github: {
|
github: {
|
||||||
username: 'arifszn', // Your GitHub org/user name. (Required)
|
username: 'arifszn', // Your GitHub org/user name. (This is the only required config)
|
||||||
sortBy: 'stars', // stars | updated
|
},
|
||||||
|
/**
|
||||||
|
* If you are deploying to https://<USERNAME>.github.io/, for example your repository is at https://github.com/arifszn/arifszn.github.io, set base to '/'.
|
||||||
|
* If you are deploying to https://<USERNAME>.github.io/<REPO_NAME>/,
|
||||||
|
* for example your repository is at https://github.com/arifszn/portfolio, then set base to '/portfolio/'.
|
||||||
|
*/
|
||||||
|
base: '/gitprofile/',
|
||||||
|
projects: {
|
||||||
|
github: {
|
||||||
|
display: true, // Display GitHub projects?
|
||||||
|
header: 'Github Projects',
|
||||||
|
mode: 'automatic', // Mode can be: 'automatic' or 'manual'
|
||||||
|
automatic: {
|
||||||
|
sortBy: 'stars', // Sort projects by 'stars' or 'updated'
|
||||||
limit: 8, // How many projects to display.
|
limit: 8, // How many projects to display.
|
||||||
exclude: {
|
exclude: {
|
||||||
forks: false, // Forked projects will not be displayed if set to true.
|
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: ['arifszn/my-project1', 'arifszn/my-project2']
|
||||||
}
|
},
|
||||||
|
},
|
||||||
|
manual: {
|
||||||
|
// Properties for manually specifying projects
|
||||||
|
projects: ['arifszn/gitprofile', 'arifszn/pandora'], // List of repository names to display. example: ['arifszn/my-project1', 'arifszn/my-project2']
|
||||||
|
},
|
||||||
|
},
|
||||||
|
external: {
|
||||||
|
header: 'My Projects',
|
||||||
|
// To hide the `External Projects` section, keep it empty.
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
title: 'Project Name',
|
||||||
|
description:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod, nunc ut.',
|
||||||
|
imageUrl:
|
||||||
|
'https://img.freepik.com/free-vector/illustration-gallery-icon_53876-27002.jpg',
|
||||||
|
link: 'https://example.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Project Name',
|
||||||
|
description:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod, nunc ut.',
|
||||||
|
imageUrl:
|
||||||
|
'https://img.freepik.com/free-vector/illustration-gallery-icon_53876-27002.jpg',
|
||||||
|
link: 'https://example.com',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
seo: {
|
||||||
|
title: 'Portfolio of Ariful Alam',
|
||||||
|
description: '',
|
||||||
|
imageURL: '',
|
||||||
},
|
},
|
||||||
social: {
|
social: {
|
||||||
linkedin: '',
|
linkedin: 'ariful-alam',
|
||||||
twitter: '',
|
twitter: 'arif_szn',
|
||||||
|
mastodon: 'arifszn@mastodon.social',
|
||||||
facebook: '',
|
facebook: '',
|
||||||
|
instagram: '',
|
||||||
|
youtube: '', // example: 'pewdiepie'
|
||||||
dribbble: '',
|
dribbble: '',
|
||||||
behance: '',
|
behance: '',
|
||||||
medium: '',
|
medium: 'arifszn',
|
||||||
devto: '',
|
dev: 'arifszn',
|
||||||
website: '',
|
stackoverflow: '', // example: '1/jeff-atwood'
|
||||||
|
skype: '',
|
||||||
|
telegram: '',
|
||||||
|
website: 'https://www.arifszn.com',
|
||||||
phone: '',
|
phone: '',
|
||||||
email: ''
|
email: 'arifulalamszn@gmail.com',
|
||||||
|
},
|
||||||
|
resume: {
|
||||||
|
fileUrl:
|
||||||
|
'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf', // Empty fileUrl will hide the `Download Resume` button.
|
||||||
},
|
},
|
||||||
skills: [
|
skills: [
|
||||||
|
'PHP',
|
||||||
|
'Laravel',
|
||||||
'JavaScript',
|
'JavaScript',
|
||||||
'React.js',
|
'React.js',
|
||||||
|
'Node.js',
|
||||||
|
'Nest.js',
|
||||||
|
'MySQL',
|
||||||
|
'PostgreSQL',
|
||||||
|
'Git',
|
||||||
|
'Docker',
|
||||||
|
'PHPUnit',
|
||||||
|
'CSS',
|
||||||
|
'Antd',
|
||||||
|
'Tailwind',
|
||||||
],
|
],
|
||||||
experiences: [
|
experiences: [
|
||||||
{
|
{
|
||||||
company: 'Company name 1',
|
company: 'Company Name',
|
||||||
position: 'Software Engineer',
|
position: 'Position',
|
||||||
|
from: 'September 2021',
|
||||||
|
to: 'Present',
|
||||||
|
companyLink: 'https://example.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
company: 'Company Name',
|
||||||
|
position: 'Position',
|
||||||
from: 'July 2019',
|
from: 'July 2019',
|
||||||
to: 'Present'
|
to: 'August 2021',
|
||||||
|
companyLink: 'https://example.com',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
company: 'Company name 2',
|
|
||||||
position: 'Jr. Software Engineer',
|
|
||||||
from: 'January 2019',
|
|
||||||
to: ' June 2019'
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
education: [
|
certifications: [
|
||||||
{
|
{
|
||||||
institution: 'Institution name 1',
|
name: 'Lorem ipsum',
|
||||||
degree: 'Bachelor of Science',
|
body: 'Lorem ipsum dolor sit amet',
|
||||||
|
year: 'March 2022',
|
||||||
|
link: 'https://example.com',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
educations: [
|
||||||
|
{
|
||||||
|
institution: 'Institution Name',
|
||||||
|
degree: 'Degree',
|
||||||
from: '2015',
|
from: '2015',
|
||||||
to: '2019'
|
to: '2019',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
institution: 'Institution name 2',
|
institution: 'Institution Name',
|
||||||
degree: 'Higher Secondary Certificate (HSC)',
|
degree: 'Degree',
|
||||||
from: '2012',
|
from: '2012',
|
||||||
to: '2014',
|
to: '2014',
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
|
// Display articles from your medium or dev account. (Optional)
|
||||||
blog: {
|
blog: {
|
||||||
// Display blog posts from your medium or dev.to account. (Optional)
|
source: 'dev', // medium | dev
|
||||||
source: 'dev.to', // medium | dev.to
|
username: 'arifszn', // to hide blog section, keep it empty
|
||||||
username: 'arifszn',
|
limit: 3, // How many articles to display. Max is 10.
|
||||||
limit: 5 // How many posts to display. Max is 10.
|
|
||||||
},
|
},
|
||||||
googleAnalytics: {
|
googleAnalytics: {
|
||||||
// GA3 tracking id/GA4 tag id
|
id: '', // GA3 tracking id/GA4 tag id UA-XXXXXXXXX-X | G-XXXXXXXXXX
|
||||||
id: '' // UA-XXXXXXXXX-X | G-XXXXXXXXXX
|
|
||||||
},
|
},
|
||||||
|
// Track visitor interaction and behavior. https://www.hotjar.com
|
||||||
hotjar: {
|
hotjar: {
|
||||||
id: '',
|
id: '',
|
||||||
snippetVersion : 6
|
snippetVersion: 6,
|
||||||
},
|
},
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
default: 'light',
|
defaultTheme: 'nord',
|
||||||
|
|
||||||
// Hides the theme change switch
|
// Hides the switch in the navbar
|
||||||
// Useful if you want to support a single color mode
|
// Useful if you want to support a single color mode
|
||||||
disableSwitch: false,
|
disableSwitch: false,
|
||||||
|
|
||||||
// Should we use the prefers-color-scheme media-query,
|
// Should use the prefers-color-scheme media-query,
|
||||||
// using user system preferences, instead of the hardcoded default
|
// using user system preferences, instead of the hardcoded defaultTheme
|
||||||
respectPrefersColorScheme: true,
|
respectPrefersColorScheme: false,
|
||||||
|
|
||||||
|
// Display the ring in Profile picture
|
||||||
|
displayAvatarRing: true,
|
||||||
|
|
||||||
// Available themes. To remove any theme, exclude from here.
|
// Available themes. To remove any theme, exclude from here.
|
||||||
themes: [
|
themes: [
|
||||||
@@ -184,242 +332,365 @@ module.exports = {
|
|||||||
'wireframe',
|
'wireframe',
|
||||||
'black',
|
'black',
|
||||||
'luxury',
|
'luxury',
|
||||||
'dracula'
|
'dracula',
|
||||||
]
|
'cmyk',
|
||||||
}
|
'autumn',
|
||||||
}
|
'business',
|
||||||
```
|
'acid',
|
||||||
</details>
|
'lemonade',
|
||||||
|
'night',
|
||||||
|
'coffee',
|
||||||
|
'winter',
|
||||||
|
'dim',
|
||||||
|
'nord',
|
||||||
|
'sunset',
|
||||||
|
'procyon',
|
||||||
|
],
|
||||||
|
|
||||||
|
// Custom theme, applied to `procyon` theme
|
||||||
|
customTheme: {
|
||||||
|
primary: '#fc055b',
|
||||||
|
secondary: '#219aaf',
|
||||||
|
accent: '#e8d03a',
|
||||||
|
neutral: '#2A2730',
|
||||||
|
'base-100': '#E3E3ED',
|
||||||
|
'--rounded-box': '3rem',
|
||||||
|
'--rounded-btn': '3rem',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Optional Footer. Supports plain text or HTML.
|
||||||
|
footer: `Made with <a
|
||||||
|
class="text-primary" href="https://github.com/arifszn/gitprofile"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>GitProfile</a> and ❤️`,
|
||||||
|
|
||||||
|
enablePWA: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CONFIG;
|
||||||
|
```
|
||||||
|
|
||||||
### Themes
|
### Themes
|
||||||
|
|
||||||
There are 21 themes available that can be selected from the dropdown.
|
There are 33 themes available that can be selected from the dropdown.
|
||||||
|
|
||||||
The default theme can be specified.
|
The default theme can be specified.
|
||||||
|
|
||||||
```js
|
```ts
|
||||||
// config.js
|
// gitprofile.config.ts
|
||||||
module.exports = {
|
const CONFIG = {
|
||||||
// ...
|
// ...
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
default: 'light',
|
defaultTheme: 'light',
|
||||||
// ...
|
// ...
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||

|
<p align="center">
|
||||||
|
<img src="https://github.com/arifszn/gitprofile/assets/45073703/91a2d9e6-67e5-47b4-9752-1881ac0f907f" alt="Theme Dropdown" width="50%">
|
||||||
|
</p>
|
||||||
|
|
||||||
Here are some screenshots of different themes.\
|
You can create your own custom theme by modifying these values. Theme `procyon` will have the custom styles.
|
||||||
<br/>
|
|
||||||
\
|
|
||||||
<br/>
|
|
||||||
\
|
|
||||||
<br/>
|
|
||||||
\
|
|
||||||
<br/>
|
|
||||||
\
|
|
||||||
<br/>
|
|
||||||
\
|
|
||||||
<br/>
|
|
||||||

|
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// gitprofile.config.ts
|
||||||
|
const CONFIG = {
|
||||||
|
// ...
|
||||||
|
themeConfig: {
|
||||||
|
customTheme: {
|
||||||
|
primary: '#fc055b',
|
||||||
|
secondary: '#219aaf',
|
||||||
|
accent: '#e8d03a',
|
||||||
|
neutral: '#2A2730',
|
||||||
|
'base-100': '#E3E3ED',
|
||||||
|
'--rounded-box': '3rem',
|
||||||
|
'--rounded-btn': '3rem',
|
||||||
|
},
|
||||||
|
// ...
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
### Google Analytics
|
### 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
|
```ts
|
||||||
module.exports = {
|
// gitprofile.config.ts
|
||||||
|
const CONFIG = {
|
||||||
// ...
|
// ...
|
||||||
googleAnalytics: {
|
googleAnalytics: {
|
||||||
id: ''
|
id: '',
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
Besides tracking visitors, ezFolio will track click events on projects and blog posts, and send them to Google Analytics.\
|
Besides tracking visitors, it will track `click events` on projects and blog posts, and send them to Google Analytics.
|
||||||
<br/>
|
|
||||||

|
|
||||||
|
|
||||||
### Hotjar
|
### Hotjar
|
||||||
|
|
||||||
ezProfile supports hotjar. If you do not want to use Hotjar, keep the <code>id</code> empty.
|
**GitProfile** supports [hotjar](https://www.hotjar.com) to track visitor interaction and behavior. If you do not want to use Hotjar, keep the `id` empty.
|
||||||
|
|
||||||
```js
|
```ts
|
||||||
// config.js
|
// gitprofile.config.ts
|
||||||
module.exports = {
|
const CONFIG = {
|
||||||
// ...
|
// ...
|
||||||
hotjar: {
|
hotjar: {
|
||||||
id: '',
|
id: '',
|
||||||
snippetVersion : 6
|
snippetVersion: 6,
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### Meta Tags
|
### SEO
|
||||||
|
|
||||||
Meta tags will be auto-generated from configs dynamically. However, you can also manually add meta tags in <code>public\index.html</code>
|
You can customize the meta tags for SEO in `seo`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// gitprofile.config.ts
|
||||||
|
const CONFIG = {
|
||||||
|
// ...
|
||||||
|
seo: {
|
||||||
|
title: 'Portfolio of Ariful Alam',
|
||||||
|
description: '',
|
||||||
|
imageURL: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### PWA
|
||||||
|
|
||||||
|
GitProfile is PWA enabled. The site can be installed as a Progressive Web App. To turn it off, set `enablePWA` to `false`.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
### Avatar and Bio
|
### Avatar and Bio
|
||||||
|
|
||||||
Your github avatar and bio will be displayed here.\
|
Your avatar and bio will be fetched from GitHub automatically.
|
||||||
<br/>
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
### Social Links
|
### 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, Mastodon, Facebook, Instagram, YouTube, Dribbble, Behance, Medium, dev, Stack Overflow, Skype, Telegram, personal website, phone and email.
|
||||||
```js
|
|
||||||
// config.js
|
```ts
|
||||||
module.exports = {
|
// gitprofile.config.ts
|
||||||
|
const CONFIG = {
|
||||||
// ...
|
// ...
|
||||||
social: {
|
social: {
|
||||||
linkedin: 'ariful-alam',
|
linkedin: 'ariful-alam',
|
||||||
twitter: 'arif_swozon',
|
twitter: 'arif_szn',
|
||||||
|
mastodon: 'arifszn@mastodon.social',
|
||||||
facebook: '',
|
facebook: '',
|
||||||
|
instagram: '',
|
||||||
|
youtube: '',
|
||||||
dribbble: '',
|
dribbble: '',
|
||||||
behance: '',
|
behance: '',
|
||||||
medium: '',
|
medium: '',
|
||||||
devto: '',
|
dev: '',
|
||||||
website: 'https://arifszn.github.io',
|
stackoverflow: '',
|
||||||
|
skype: '',
|
||||||
|
telegram: '',
|
||||||
|
website: '',
|
||||||
phone: '',
|
phone: '',
|
||||||
email: ''
|
email: '',
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Skills
|
### Skills
|
||||||
|
|
||||||
To showcase your skills provide them here.
|
To showcase your skills provide them here.
|
||||||
```js
|
|
||||||
// config.js
|
```ts
|
||||||
module.exports = {
|
// gitprofile.config.ts
|
||||||
|
const CONFIG = {
|
||||||
// ...
|
// ...
|
||||||
skills: [
|
skills: ['JavaScript', 'React.js'],
|
||||||
'JavaScript',
|
};
|
||||||
'React.js',
|
|
||||||
],
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Empty array will hide the skills section.
|
Empty array will hide the skills section.
|
||||||
|
|
||||||
|
|
||||||
### Experience
|
### Experience
|
||||||
|
|
||||||
Provide your job history in <code>experiences</code>.
|
Provide your job history in `experiences`.
|
||||||
```js
|
|
||||||
// config.js
|
```ts
|
||||||
module.exports = {
|
// gitprofile.config.ts
|
||||||
|
const CONFIG = {
|
||||||
// ...
|
// ...
|
||||||
experiences: [
|
experiences: [
|
||||||
{
|
{
|
||||||
company: 'Company name 1',
|
company: 'Company Name',
|
||||||
position: 'Software Engineer',
|
position: 'Position',
|
||||||
from: 'July 2019',
|
from: 'September 2021',
|
||||||
to: 'Present'
|
to: 'Present',
|
||||||
|
companyLink: 'https://example.com',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
company: 'Company name 2',
|
company: 'Company Name',
|
||||||
position: 'Jr. Software Engineer',
|
position: 'Position',
|
||||||
from: 'January 2019',
|
from: 'July 2019',
|
||||||
to: ' June 2019'
|
to: 'August 2021',
|
||||||
}
|
companyLink: 'https://example.com',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
Empty array will hide the experience section.
|
Empty array will hide the experience section.
|
||||||
|
|
||||||
|
|
||||||
### Education
|
### Education
|
||||||
|
|
||||||
Provide your education history in <code>education</code>.
|
Provide your education history in `educations`.
|
||||||
```js
|
|
||||||
// config.js
|
```ts
|
||||||
module.exports = {
|
// gitprofile.config.ts
|
||||||
|
const CONFIG = {
|
||||||
// ...
|
// ...
|
||||||
education: [
|
educations: [
|
||||||
{
|
{
|
||||||
institution: 'Institution name 1',
|
institution: 'Institution name 1',
|
||||||
degree: 'Bachelor of Science',
|
degree: 'Bachelor of Science',
|
||||||
from: '2015',
|
from: '2015',
|
||||||
to: '2019'
|
to: '2019',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
institution: 'Institution name 2',
|
institution: 'Institution name 2',
|
||||||
degree: 'Higher Secondary Certificate (HSC)',
|
degree: 'Higher Secondary Certificate (HSC)',
|
||||||
from: '2012',
|
from: '2012',
|
||||||
to: '2014',
|
to: '2014',
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
Empty array will hide the education section.
|
Empty array will hide the education section.
|
||||||
|
|
||||||
|
### Certifications
|
||||||
|
|
||||||
|
Provide your industry certifications in `certifications`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// gitprofile.config.ts
|
||||||
|
const CONFIG = {
|
||||||
|
// ...
|
||||||
|
certifications: [
|
||||||
|
{
|
||||||
|
name: 'Lorem ipsum',
|
||||||
|
body: 'Lorem ipsum dolor sit amet',
|
||||||
|
year: 'March 2022',
|
||||||
|
link: 'https://example.com',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Empty array will hide the certifications section.
|
||||||
|
|
||||||
### Projects
|
### 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.
|
#### Github Projects
|
||||||
|
|
||||||
```js
|
- **Automatic Mode:** Seamlessly showcase your top GitHub projects based on stars or last updated date.
|
||||||
// config.js
|
- **Manual Mode:** Choose specific repositories to highlight.
|
||||||
module.exports = {
|
|
||||||
|
```ts
|
||||||
|
// gitprofile.config.ts
|
||||||
|
const CONFIG = {
|
||||||
// ...
|
// ...
|
||||||
|
projects: {
|
||||||
github: {
|
github: {
|
||||||
username: 'arifszn',
|
display: true, // Display GitHub projects?
|
||||||
sortBy: 'stars',
|
header: 'Github Projects',
|
||||||
limit: 8,
|
mode: 'automatic', // Mode can be: 'automatic' or 'manual'
|
||||||
|
automatic: {
|
||||||
|
sortBy: 'stars', // Sort projects by 'stars' or 'updated'
|
||||||
|
limit: 8, // How many projects to display.
|
||||||
exclude: {
|
exclude: {
|
||||||
forks: false,
|
forks: false, // Forked projects will not be displayed if set to true.
|
||||||
projects: ['my-project1', 'my-project2']
|
projects: [], // These projects will not be displayed. example: ['arifszn/my-project1', 'arifszn/my-project2']
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
manual: {
|
||||||
|
// Properties for manually specifying projects
|
||||||
|
projects: ['arifszn/gitprofile', 'arifszn/pandora'], // List of repository names to display. example: ['arifszn/my-project1', 'arifszn/my-project2']
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### External Projects
|
||||||
|
|
||||||
|
- **Highlight Projects Beyond GitHub:** Feature projects hosted on other platforms or personal websites.
|
||||||
|
- **Control over Content:** Provide custom titles, descriptions, images, and links for each external project.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// gitprofile.config.ts
|
||||||
|
const CONFIG = {
|
||||||
|
// ...
|
||||||
|
projects: {
|
||||||
|
external: {
|
||||||
|
header: 'My Projects',
|
||||||
|
// To hide the `External Projects` section, keep it empty.
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
title: 'Project Name',
|
||||||
|
description:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod, nunc ut.',
|
||||||
|
imageUrl:
|
||||||
|
'https://img.freepik.com/free-vector/illustration-gallery-icon_53876-27002.jpg',
|
||||||
|
link: 'https://example.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Project Name',
|
||||||
|
description:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod, nunc ut.',
|
||||||
|
imageUrl:
|
||||||
|
'https://img.freepik.com/free-vector/illustration-gallery-icon_53876-27002.jpg',
|
||||||
|
link: 'https://example.com',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### Blog Posts
|
### 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
|
```ts
|
||||||
// config.js
|
// gitprofile.config.ts
|
||||||
module.exports = {
|
const CONFIG = {
|
||||||
// ...
|
// ...
|
||||||
blog: {
|
blog: {
|
||||||
source: 'dev.to',
|
source: 'dev',
|
||||||
username: 'arifszn',
|
username: 'arifszn',
|
||||||
limit: 5
|
limit: 5,
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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 or removing/changing the footer notice. However You are open to use this project by forking it and change any code necessary by giving attribute to the original author.
|
|
||||||
|
|
||||||
|
The posts are fetched by [blog.js](https://github.com/arifszn/blog.js).
|
||||||
|
|
||||||
## 💖 Support
|
## 💖 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.
|
<p>You can show your support by starring this project. ★</p>
|
||||||
|
<a href="https://github.com/arifszn/gitprofile/stargazers">
|
||||||
|
<img src="https://img.shields.io/github/stars/arifszn/gitprofile?style=social" alt="Github Star">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
## 💡 Contribute
|
||||||
|
|
||||||
## 💡 Contributing
|
To contribute, see the [Contributing guide](https://github.com/arifszn/gitprofile/blob/main/CONTRIBUTING.md).
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## 📄 License
|
## 📄 License
|
||||||
|
|
||||||
ezProfile is licensed under the [Apache-2.0 License](https://github.com/arifszn/ezprofile/blob/main/LICENSE).
|
[MIT](https://github.com/arifszn/gitprofile/blob/main/LICENSE)
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
style: {
|
|
||||||
postcss: {
|
|
||||||
plugins: [
|
|
||||||
require('tailwindcss'),
|
|
||||||
require('autoprefixer'),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
18
docker-compose.yml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./node_modules/vail/runtimes/npm/Dockerfile
|
||||||
|
args:
|
||||||
|
VAIL_NODE_VERSION: 20
|
||||||
|
command: npm run dev -- --host 0.0.0.0
|
||||||
|
ports:
|
||||||
|
- '5173:5173'
|
||||||
|
volumes:
|
||||||
|
- .:/var/www/html
|
||||||
|
networks:
|
||||||
|
- vail
|
||||||
|
networks:
|
||||||
|
vail:
|
||||||
|
driver: bridge
|
||||||
222
gitprofile.config.ts
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
// gitprofile.config.ts
|
||||||
|
|
||||||
|
const CONFIG = {
|
||||||
|
github: {
|
||||||
|
username: 'arifszn', // Your GitHub org/user name. (This is the only required config)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* If you are deploying to https://<USERNAME>.github.io/, for example your repository is at https://github.com/arifszn/arifszn.github.io, set base to '/'.
|
||||||
|
* If you are deploying to https://<USERNAME>.github.io/<REPO_NAME>/,
|
||||||
|
* for example your repository is at https://github.com/arifszn/portfolio, then set base to '/portfolio/'.
|
||||||
|
*/
|
||||||
|
base: '/gitprofile/',
|
||||||
|
projects: {
|
||||||
|
github: {
|
||||||
|
display: true, // Display GitHub projects?
|
||||||
|
header: 'Github Projects',
|
||||||
|
mode: 'automatic', // Mode can be: 'automatic' or 'manual'
|
||||||
|
automatic: {
|
||||||
|
sortBy: 'stars', // Sort projects by 'stars' or '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: ['arifszn/my-project1', 'arifszn/my-project2']
|
||||||
|
},
|
||||||
|
},
|
||||||
|
manual: {
|
||||||
|
// Properties for manually specifying projects
|
||||||
|
projects: ['arifszn/gitprofile', 'arifszn/pandora'], // List of repository names to display. example: ['arifszn/my-project1', 'arifszn/my-project2']
|
||||||
|
},
|
||||||
|
},
|
||||||
|
external: {
|
||||||
|
header: 'My Projects',
|
||||||
|
// To hide the `External Projects` section, keep it empty.
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
title: 'Project Name',
|
||||||
|
description:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod, nunc ut.',
|
||||||
|
imageUrl:
|
||||||
|
'https://img.freepik.com/free-vector/illustration-gallery-icon_53876-27002.jpg',
|
||||||
|
link: 'https://example.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Project Name',
|
||||||
|
description:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod, nunc ut.',
|
||||||
|
imageUrl:
|
||||||
|
'https://img.freepik.com/free-vector/illustration-gallery-icon_53876-27002.jpg',
|
||||||
|
link: 'https://example.com',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
seo: {
|
||||||
|
title: 'Portfolio of Ariful Alam',
|
||||||
|
description: '',
|
||||||
|
imageURL: '',
|
||||||
|
},
|
||||||
|
social: {
|
||||||
|
linkedin: 'ariful-alam',
|
||||||
|
twitter: 'arif_szn',
|
||||||
|
mastodon: 'arifszn@mastodon.social',
|
||||||
|
facebook: '',
|
||||||
|
instagram: '',
|
||||||
|
youtube: '', // example: 'pewdiepie'
|
||||||
|
dribbble: '',
|
||||||
|
behance: '',
|
||||||
|
medium: 'arifszn',
|
||||||
|
dev: 'arifszn',
|
||||||
|
stackoverflow: '', // example: '1/jeff-atwood'
|
||||||
|
skype: '',
|
||||||
|
telegram: '',
|
||||||
|
website: 'https://www.arifszn.com',
|
||||||
|
phone: '',
|
||||||
|
email: 'arifulalamszn@gmail.com',
|
||||||
|
},
|
||||||
|
resume: {
|
||||||
|
fileUrl:
|
||||||
|
'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf', // Empty fileUrl will hide the `Download Resume` button.
|
||||||
|
},
|
||||||
|
skills: [
|
||||||
|
'PHP',
|
||||||
|
'Laravel',
|
||||||
|
'JavaScript',
|
||||||
|
'React.js',
|
||||||
|
'Node.js',
|
||||||
|
'Nest.js',
|
||||||
|
'MySQL',
|
||||||
|
'PostgreSQL',
|
||||||
|
'Git',
|
||||||
|
'Docker',
|
||||||
|
'PHPUnit',
|
||||||
|
'CSS',
|
||||||
|
'Antd',
|
||||||
|
'Tailwind',
|
||||||
|
],
|
||||||
|
experiences: [
|
||||||
|
{
|
||||||
|
company: 'Company Name',
|
||||||
|
position: 'Position',
|
||||||
|
from: 'September 2021',
|
||||||
|
to: 'Present',
|
||||||
|
companyLink: 'https://example.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
company: 'Company Name',
|
||||||
|
position: 'Position',
|
||||||
|
from: 'July 2019',
|
||||||
|
to: 'August 2021',
|
||||||
|
companyLink: 'https://example.com',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
certifications: [
|
||||||
|
{
|
||||||
|
name: 'Lorem ipsum',
|
||||||
|
body: 'Lorem ipsum dolor sit amet',
|
||||||
|
year: 'March 2022',
|
||||||
|
link: 'https://example.com',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
educations: [
|
||||||
|
{
|
||||||
|
institution: 'Institution Name',
|
||||||
|
degree: 'Degree',
|
||||||
|
from: '2015',
|
||||||
|
to: '2019',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
institution: 'Institution Name',
|
||||||
|
degree: 'Degree',
|
||||||
|
from: '2012',
|
||||||
|
to: '2014',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// Display articles from your medium or dev account. (Optional)
|
||||||
|
blog: {
|
||||||
|
source: 'dev', // medium | dev
|
||||||
|
username: 'arifszn', // to hide blog section, keep it empty
|
||||||
|
limit: 3, // How many articles to display. Max is 10.
|
||||||
|
},
|
||||||
|
googleAnalytics: {
|
||||||
|
id: '', // GA3 tracking id/GA4 tag id UA-XXXXXXXXX-X | G-XXXXXXXXXX
|
||||||
|
},
|
||||||
|
// Track visitor interaction and behavior. https://www.hotjar.com
|
||||||
|
hotjar: {
|
||||||
|
id: '',
|
||||||
|
snippetVersion: 6,
|
||||||
|
},
|
||||||
|
themeConfig: {
|
||||||
|
defaultTheme: 'nord',
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
|
||||||
|
// Display the ring in Profile picture
|
||||||
|
displayAvatarRing: 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',
|
||||||
|
'cmyk',
|
||||||
|
'autumn',
|
||||||
|
'business',
|
||||||
|
'acid',
|
||||||
|
'lemonade',
|
||||||
|
'night',
|
||||||
|
'coffee',
|
||||||
|
'winter',
|
||||||
|
'dim',
|
||||||
|
'nord',
|
||||||
|
'sunset',
|
||||||
|
'procyon',
|
||||||
|
],
|
||||||
|
|
||||||
|
// Custom theme, applied to `procyon` theme
|
||||||
|
customTheme: {
|
||||||
|
primary: '#fc055b',
|
||||||
|
secondary: '#219aaf',
|
||||||
|
accent: '#e8d03a',
|
||||||
|
neutral: '#2A2730',
|
||||||
|
'base-100': '#E3E3ED',
|
||||||
|
'--rounded-box': '3rem',
|
||||||
|
'--rounded-btn': '3rem',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Optional Footer. Supports plain text or HTML.
|
||||||
|
footer: `Made with <a
|
||||||
|
class="text-primary" href="https://github.com/arifszn/gitprofile"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>GitProfile</a> and ❤️`,
|
||||||
|
|
||||||
|
enablePWA: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CONFIG;
|
||||||
409
global.d.ts
vendored
Normal file
@@ -0,0 +1,409 @@
|
|||||||
|
interface Github {
|
||||||
|
/**
|
||||||
|
* GitHub org/user name
|
||||||
|
*/
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GitHubProjects {
|
||||||
|
/**
|
||||||
|
* Display GitHub projects?
|
||||||
|
*/
|
||||||
|
display?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Header
|
||||||
|
*/
|
||||||
|
header?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 'automatic' | 'manual'
|
||||||
|
*/
|
||||||
|
mode?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config of automatic mode
|
||||||
|
*/
|
||||||
|
automatic?: {
|
||||||
|
/**
|
||||||
|
* '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>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config of manual mode
|
||||||
|
*/
|
||||||
|
manual?: {
|
||||||
|
/**
|
||||||
|
* These projects will be displayed
|
||||||
|
*
|
||||||
|
* example: ['my-project1', 'my-project2']
|
||||||
|
*/
|
||||||
|
projects?: Array<string>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExternalProjects {
|
||||||
|
/**
|
||||||
|
* Header
|
||||||
|
*/
|
||||||
|
header?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project list
|
||||||
|
*/
|
||||||
|
projects?: {
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
imageUrl?: string;
|
||||||
|
link: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Projects {
|
||||||
|
github?: GitHubProjects;
|
||||||
|
|
||||||
|
external?: ExternalProjects;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SEO {
|
||||||
|
/**
|
||||||
|
* Meta title
|
||||||
|
*/
|
||||||
|
title?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Meta description
|
||||||
|
*/
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Meta image
|
||||||
|
*/
|
||||||
|
imageURL?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Social {
|
||||||
|
/**
|
||||||
|
* LinkedIn
|
||||||
|
*/
|
||||||
|
linkedin?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Twitter
|
||||||
|
*/
|
||||||
|
twitter?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mastodon
|
||||||
|
*/
|
||||||
|
mastodon?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Facebook
|
||||||
|
*/
|
||||||
|
facebook?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instagram
|
||||||
|
*/
|
||||||
|
instagram?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* YouTube
|
||||||
|
*/
|
||||||
|
youtube?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dribbble
|
||||||
|
*/
|
||||||
|
dribbble?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Behance
|
||||||
|
*/
|
||||||
|
behance?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Medium
|
||||||
|
*/
|
||||||
|
medium?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dev
|
||||||
|
*/
|
||||||
|
dev?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stack Overflow
|
||||||
|
*/
|
||||||
|
stackoverflow?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Website
|
||||||
|
*/
|
||||||
|
website?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skype username
|
||||||
|
*/
|
||||||
|
skype?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Telegram username
|
||||||
|
*/
|
||||||
|
telegram?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phone
|
||||||
|
*/
|
||||||
|
phone?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Email
|
||||||
|
*/
|
||||||
|
email?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Resume {
|
||||||
|
/**
|
||||||
|
* Resume file urlm
|
||||||
|
*/
|
||||||
|
fileUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Experience {
|
||||||
|
company?: string;
|
||||||
|
position?: string;
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
companyLink?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Certification {
|
||||||
|
body?: string;
|
||||||
|
name?: string;
|
||||||
|
year?: string;
|
||||||
|
link?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Education {
|
||||||
|
institution?: string;
|
||||||
|
degree?: string;
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GoogleAnalytics {
|
||||||
|
/**
|
||||||
|
* GA3 tracking id/GA4 tag id UA-XXXXXXXXX-X | G-XXXXXXXXXX
|
||||||
|
*/
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Hotjar {
|
||||||
|
/**
|
||||||
|
* Hotjar id
|
||||||
|
*/
|
||||||
|
id?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Snippet Version
|
||||||
|
*/
|
||||||
|
snippetVersion?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Blog {
|
||||||
|
/**
|
||||||
|
* medium | dev
|
||||||
|
*/
|
||||||
|
source?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Username
|
||||||
|
*/
|
||||||
|
username?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many articles to display
|
||||||
|
*
|
||||||
|
* Max is 10
|
||||||
|
*/
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ThemeConfig {
|
||||||
|
/**
|
||||||
|
* Default theme
|
||||||
|
*/
|
||||||
|
defaultTheme?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the switch in the navbar
|
||||||
|
*/
|
||||||
|
disableSwitch?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should use the prefers-color-scheme media-query
|
||||||
|
*/
|
||||||
|
respectPrefersColorScheme?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the ring in Profile picture
|
||||||
|
*/
|
||||||
|
displayAvatarRing?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Available themes
|
||||||
|
*/
|
||||||
|
themes?: Array<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom theme
|
||||||
|
*/
|
||||||
|
customTheme?: CustomTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Config {
|
||||||
|
/**
|
||||||
|
* GitHub config
|
||||||
|
*/
|
||||||
|
github: Github;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vite's base url
|
||||||
|
*/
|
||||||
|
base?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Projects config
|
||||||
|
*/
|
||||||
|
projects?: Projects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SEO config
|
||||||
|
*/
|
||||||
|
seo?: SEO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Social links
|
||||||
|
*/
|
||||||
|
social?: Social;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skill list
|
||||||
|
*/
|
||||||
|
skills?: Array<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Experience list
|
||||||
|
*/
|
||||||
|
experiences?: Array<Experience>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Certifications list
|
||||||
|
*/
|
||||||
|
certifications?: Array<Certification>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Education list
|
||||||
|
*/
|
||||||
|
educations?: Array<Education>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resume
|
||||||
|
*/
|
||||||
|
resume?: Resume;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Google Analytics config
|
||||||
|
*/
|
||||||
|
googleAnalytics?: GoogleAnalytics;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hotjar config
|
||||||
|
*/
|
||||||
|
hotjar?: Hotjar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blog config
|
||||||
|
*/
|
||||||
|
blog?: Blog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme config
|
||||||
|
*/
|
||||||
|
themeConfig?: ThemeConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom footer
|
||||||
|
*/
|
||||||
|
footer?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable PWA
|
||||||
|
*/
|
||||||
|
enablePWA?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const CONFIG: Config;
|
||||||
31
index.html
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<!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" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title><%- metaTitle %></title>
|
||||||
|
|
||||||
|
<meta property="title" content="<%- metaTitle %>" />
|
||||||
|
<meta name="description" content="<%- metaDescription %>" />
|
||||||
|
|
||||||
|
<meta itemprop="name" content="<%- metaTitle %>" />
|
||||||
|
<meta itemprop="description" content="<%- metaDescription %>" />
|
||||||
|
<meta itemprop="image" content="<%- metaImageURL %>" />
|
||||||
|
|
||||||
|
<meta property="og:title" content="<%- metaTitle %>" />
|
||||||
|
<meta property="og:description" content="<%- metaDescription %>" />
|
||||||
|
<meta property="og:image" content="<%- metaImageURL %>" />
|
||||||
|
<meta property="og:image:secure_url" content="<%- metaImageURL %>" />
|
||||||
|
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta name="twitter:title" content="<%- metaTitle %>" />
|
||||||
|
<meta name="twitter:description" content="<%- metaDescription %>" />
|
||||||
|
<meta name="twitter:image" content="<%- metaImageURL %>" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
39932
package-lock.json
generated
111
package.json
@@ -1,70 +1,67 @@
|
|||||||
{
|
{
|
||||||
"name": "ezprofile",
|
"name": "@arifszn/gitprofile",
|
||||||
"version": "1.1.0",
|
"description": "Create an automatic portfolio based on GitHub profile",
|
||||||
"description": "Kickstart your personal portfolio with Github Api and blog",
|
"version": "3.0.0",
|
||||||
"homepage": "https://arifszn.github.io/ezprofile",
|
"type": "module",
|
||||||
"private": true,
|
"license": "MIT",
|
||||||
"license": "Apache-2.0",
|
|
||||||
"author": "arifszn",
|
"author": "arifszn",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/arifszn/ezprofile.git"
|
"url": "https://github.com/arifszn/gitprofile.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"bugs": {
|
||||||
"@craco/craco": "^6.2.0",
|
"url": "https://github.com/arifszn/gitprofile/issues"
|
||||||
"@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": {
|
"scripts": {
|
||||||
"start": "craco start",
|
"dev": "vite",
|
||||||
"build": "craco build",
|
"build": "tsc && vite build",
|
||||||
"test": "craco test",
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"eject": "react-scripts eject",
|
"lint:fix": "eslint . --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"predeploy": "npm run build",
|
"prettier": "prettier --check \"./**/*.{js,jsx,ts,tsx,css,md,json}\"",
|
||||||
"deploy": "gh-pages -d build"
|
"prettier:fix": "prettier --write \"./**/*.{js,jsx,ts,tsx,css,md,json}\"",
|
||||||
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"dependencies": {
|
||||||
"extends": [
|
"react": "^18.2.0",
|
||||||
"react-app",
|
"react-dom": "^18.2.0"
|
||||||
"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": {
|
"devDependencies": {
|
||||||
"autoprefixer": "^9.8.6",
|
"@arifszn/blog-js": "^2.0.5",
|
||||||
"postcss": "^7.0.36",
|
"@types/react": "^18.2.55",
|
||||||
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.7"
|
"@types/react-dom": "^18.2.17",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
||||||
|
"@typescript-eslint/parser": "^6.14.0",
|
||||||
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
|
"autoprefixer": "^10.4.17",
|
||||||
|
"axios": "^1.6.7",
|
||||||
|
"daisyui": "^4.6.0",
|
||||||
|
"date-fns": "^3.3.1",
|
||||||
|
"eslint": "^8.55.0",
|
||||||
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.5",
|
||||||
|
"postcss": "^8.4.33",
|
||||||
|
"prettier": "^3.2.5",
|
||||||
|
"react-helmet-async": "^2.0.4",
|
||||||
|
"react-hotjar": "^6.2.0",
|
||||||
|
"react-icons": "^5.0.1",
|
||||||
|
"tailwindcss": "^3.4.1",
|
||||||
|
"typescript": "^5.2.2",
|
||||||
|
"vail": "^1.0.3",
|
||||||
|
"vite": "^5.0.12",
|
||||||
|
"vite-plugin-html": "^3.2.2",
|
||||||
|
"vite-plugin-pwa": "^0.18.1"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
"git-profile",
|
||||||
|
"gitprofile",
|
||||||
|
"gitportfolio",
|
||||||
"personal-site",
|
"personal-site",
|
||||||
"template",
|
"template",
|
||||||
"portfolio",
|
"portfolio",
|
||||||
|
"resume",
|
||||||
|
"cv",
|
||||||
"personal-website",
|
"personal-website",
|
||||||
"portfolio-website",
|
"portfolio-website",
|
||||||
"portfolio-site",
|
"portfolio-site",
|
||||||
@@ -73,7 +70,17 @@
|
|||||||
"developer-portfolio",
|
"developer-portfolio",
|
||||||
"portfolio-project",
|
"portfolio-project",
|
||||||
"github-portfolio",
|
"github-portfolio",
|
||||||
|
"tailwind-portfolio",
|
||||||
|
"vite-portfolio",
|
||||||
|
"projects",
|
||||||
|
"open-source",
|
||||||
|
"git",
|
||||||
"react-portfolio",
|
"react-portfolio",
|
||||||
|
"github",
|
||||||
|
"github-page",
|
||||||
|
"github-pages",
|
||||||
|
"github-portfolio",
|
||||||
|
"vite-portfolio",
|
||||||
"github-api"
|
"github-api"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
6
postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 434 B After Width: | Height: | Size: 655 B |
|
Before Width: | Height: | Size: 767 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -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>
|
|
||||||
BIN
public/logo.png
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 20 KiB |
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"short_name": "ezProfile",
|
|
||||||
"name": "Personal Portfolio",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "favicon.ico",
|
|
||||||
"sizes": "64x64 32x32 24x24 16x16",
|
|
||||||
"type": "image/x-icon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "logo192.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "192x192"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "logo512.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "512x512"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"start_url": ".",
|
|
||||||
"display": "standalone",
|
|
||||||
"theme_color": "#7a44ee",
|
|
||||||
"background_color": "#ffffff"
|
|
||||||
}
|
|
||||||
178
src/App.js
@@ -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 */}
|
|
||||||
<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;
|
|
||||||
@@ -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
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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('ezprofileTheme', 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;
|
|
||||||
100
src/components/avatar-card/index.tsx
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { FALLBACK_IMAGE } from '../../constants';
|
||||||
|
import { Profile } from '../../interfaces/profile';
|
||||||
|
import { skeleton } from '../../utils';
|
||||||
|
import LazyImage from '../lazy-image';
|
||||||
|
|
||||||
|
interface AvatarCardProps {
|
||||||
|
profile: Profile | null;
|
||||||
|
loading: boolean;
|
||||||
|
avatarRing: boolean;
|
||||||
|
resumeFileUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders an AvatarCard component.
|
||||||
|
* @param profile - The profile object.
|
||||||
|
* @param loading - A boolean indicating if the profile is loading.
|
||||||
|
* @param avatarRing - A boolean indicating if the avatar should have a ring.
|
||||||
|
* @param resumeFileUrl - The URL of the resume file.
|
||||||
|
* @returns JSX element representing the AvatarCard.
|
||||||
|
*/
|
||||||
|
const AvatarCard: React.FC<AvatarCardProps> = ({
|
||||||
|
profile,
|
||||||
|
loading,
|
||||||
|
avatarRing,
|
||||||
|
resumeFileUrl,
|
||||||
|
}): JSX.Element => {
|
||||||
|
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({
|
||||||
|
widthCls: 'w-full',
|
||||||
|
heightCls: 'h-full',
|
||||||
|
shape: '',
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="avatar opacity-90">
|
||||||
|
<div
|
||||||
|
className={`mb-8 rounded-full w-32 h-32 ${
|
||||||
|
avatarRing
|
||||||
|
? 'ring ring-primary ring-offset-base-100 ring-offset-2'
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
<LazyImage
|
||||||
|
src={profile.avatar ? profile.avatar : FALLBACK_IMAGE}
|
||||||
|
alt={profile.name}
|
||||||
|
placeholder={skeleton({
|
||||||
|
widthCls: 'w-full',
|
||||||
|
heightCls: 'h-full',
|
||||||
|
shape: '',
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="text-center mx-auto px-8">
|
||||||
|
<h5 className="font-bold text-2xl">
|
||||||
|
{loading || !profile ? (
|
||||||
|
skeleton({ widthCls: 'w-48', heightCls: 'h-8' })
|
||||||
|
) : (
|
||||||
|
<span className="text-base-content opacity-70">
|
||||||
|
{profile.name}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</h5>
|
||||||
|
<div className="mt-3 text-base-content text-opacity-60 font-mono">
|
||||||
|
{loading || !profile
|
||||||
|
? skeleton({ widthCls: 'w-48', heightCls: 'h-5' })
|
||||||
|
: profile.bio}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{resumeFileUrl &&
|
||||||
|
(loading ? (
|
||||||
|
<div className="mt-6">
|
||||||
|
{skeleton({ widthCls: 'w-40', heightCls: 'h-8' })}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<a
|
||||||
|
href={resumeFileUrl}
|
||||||
|
target="_blank"
|
||||||
|
className="btn btn-outline btn-sm text-xs mt-6 opacity-50"
|
||||||
|
download
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Download Resume
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AvatarCard;
|
||||||
209
src/components/blog-card/index.tsx
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import LazyImage from '../lazy-image';
|
||||||
|
import { AiOutlineContainer } from 'react-icons/ai';
|
||||||
|
import { getDevPost, getMediumPost } from '@arifszn/blog-js';
|
||||||
|
import { formatDistance } from 'date-fns';
|
||||||
|
import { SanitizedBlog } from '../../interfaces/sanitized-config';
|
||||||
|
import { ga, skeleton } from '../../utils';
|
||||||
|
import { Article } from '../../interfaces/article';
|
||||||
|
|
||||||
|
const BlogCard = ({
|
||||||
|
loading,
|
||||||
|
blog,
|
||||||
|
googleAnalyticsId,
|
||||||
|
}: {
|
||||||
|
loading: boolean;
|
||||||
|
blog: SanitizedBlog;
|
||||||
|
googleAnalyticsId?: string;
|
||||||
|
}) => {
|
||||||
|
const [articles, setArticles] = useState<Article[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [blog.source, blog.username]);
|
||||||
|
|
||||||
|
const renderSkeleton = () => {
|
||||||
|
const 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({
|
||||||
|
widthCls: 'w-full',
|
||||||
|
heightCls: 'h-full',
|
||||||
|
shape: '',
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-full">
|
||||||
|
<div className="flex items-start px-4">
|
||||||
|
<div className="w-full">
|
||||||
|
<h2>
|
||||||
|
{skeleton({
|
||||||
|
widthCls: 'w-full',
|
||||||
|
heightCls: 'h-8',
|
||||||
|
className: 'mb-2 mx-auto md:mx-0',
|
||||||
|
})}
|
||||||
|
</h2>
|
||||||
|
{skeleton({
|
||||||
|
widthCls: 'w-24',
|
||||||
|
heightCls: 'h-3',
|
||||||
|
className: 'mx-auto md:mx-0',
|
||||||
|
})}
|
||||||
|
<div className="mt-3">
|
||||||
|
{skeleton({
|
||||||
|
widthCls: 'w-full',
|
||||||
|
heightCls: 'h-4',
|
||||||
|
className: 'mx-auto md:mx-0',
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 flex items-center flex-wrap justify-center md:justify-start">
|
||||||
|
{skeleton({
|
||||||
|
widthCls: 'w-32',
|
||||||
|
heightCls: '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 (googleAnalyticsId) {
|
||||||
|
ga.event('Click Blog Post', {
|
||||||
|
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({
|
||||||
|
widthCls: 'w-full',
|
||||||
|
heightCls: '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="text-base-content opacity-50 text-xs">
|
||||||
|
{formatDistance(article.publishedAt, new Date(), {
|
||||||
|
addSuffix: true,
|
||||||
|
})}
|
||||||
|
</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 text-base-content"
|
||||||
|
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 text-base-content">
|
||||||
|
No recent post
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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 ${
|
||||||
|
loading || (articles && articles.length)
|
||||||
|
? 'shadow bg-opacity-40'
|
||||||
|
: 'shadow-lg'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="card-body">
|
||||||
|
<div className="mx-3 mb-2">
|
||||||
|
<h5 className="card-title">
|
||||||
|
{loading ? (
|
||||||
|
skeleton({ widthCls: 'w-28', heightCls: 'h-8' })
|
||||||
|
) : (
|
||||||
|
<span className="text-base-content opacity-70">
|
||||||
|
My Articles
|
||||||
|
</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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BlogCard;
|
||||||
99
src/components/certification-card/index.tsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { SanitizedCertification } from '../../interfaces/sanitized-config';
|
||||||
|
import { skeleton } from '../../utils';
|
||||||
|
|
||||||
|
const ListItem = ({
|
||||||
|
year,
|
||||||
|
name,
|
||||||
|
body,
|
||||||
|
link,
|
||||||
|
}: {
|
||||||
|
year?: React.ReactNode;
|
||||||
|
name?: React.ReactNode;
|
||||||
|
body?: React.ReactNode;
|
||||||
|
link?: string;
|
||||||
|
}) => (
|
||||||
|
<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">{year}</div>
|
||||||
|
<div className="font-semibold">
|
||||||
|
<a href={link} target="_blank" rel="noreferrer">
|
||||||
|
{name}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<h3 className="mb-4 font-normal">{body}</h3>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
|
||||||
|
const CertificationCard = ({
|
||||||
|
certifications,
|
||||||
|
loading,
|
||||||
|
}: {
|
||||||
|
certifications: SanitizedCertification[];
|
||||||
|
loading: boolean;
|
||||||
|
}) => {
|
||||||
|
const renderSkeleton = () => {
|
||||||
|
const array = [];
|
||||||
|
for (let index = 0; index < 2; index++) {
|
||||||
|
array.push(
|
||||||
|
<ListItem
|
||||||
|
key={index}
|
||||||
|
year={skeleton({
|
||||||
|
widthCls: 'w-5/12',
|
||||||
|
heightCls: 'h-4',
|
||||||
|
})}
|
||||||
|
name={skeleton({
|
||||||
|
widthCls: 'w-6/12',
|
||||||
|
heightCls: 'h-4',
|
||||||
|
className: 'my-1.5',
|
||||||
|
})}
|
||||||
|
body={skeleton({ widthCls: 'w-6/12', heightCls: 'h-3' })}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card shadow-lg compact bg-base-100">
|
||||||
|
<div className="card-body">
|
||||||
|
<div className="mx-3">
|
||||||
|
<h5 className="card-title">
|
||||||
|
{loading ? (
|
||||||
|
skeleton({ widthCls: 'w-32', heightCls: 'h-8' })
|
||||||
|
) : (
|
||||||
|
<span className="text-base-content opacity-70">
|
||||||
|
Certification
|
||||||
|
</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()
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{certifications.map((certification, index) => (
|
||||||
|
<ListItem
|
||||||
|
key={index}
|
||||||
|
year={`${certification.year}`}
|
||||||
|
name={certification.name}
|
||||||
|
body={certification.body}
|
||||||
|
link={certification.link ? certification.link : undefined}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CertificationCard;
|
||||||
293
src/components/details-card/index.tsx
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
import { MdLocationOn } from 'react-icons/md';
|
||||||
|
import {
|
||||||
|
AiFillGithub,
|
||||||
|
AiFillInstagram,
|
||||||
|
AiFillMediumSquare,
|
||||||
|
} from 'react-icons/ai';
|
||||||
|
import { SiTwitter } from 'react-icons/si';
|
||||||
|
import { CgDribbble } from 'react-icons/cg';
|
||||||
|
import { RiPhoneFill, RiMailFill } from 'react-icons/ri';
|
||||||
|
import { Fragment } from 'react';
|
||||||
|
import {
|
||||||
|
FaBehanceSquare,
|
||||||
|
FaBuilding,
|
||||||
|
FaDev,
|
||||||
|
FaFacebook,
|
||||||
|
FaGlobe,
|
||||||
|
FaSkype,
|
||||||
|
FaMastodon,
|
||||||
|
FaStackOverflow,
|
||||||
|
FaTelegram,
|
||||||
|
FaLinkedin,
|
||||||
|
FaYoutube,
|
||||||
|
} from 'react-icons/fa';
|
||||||
|
import { skeleton } from '../../utils';
|
||||||
|
import { Profile } from '../../interfaces/profile';
|
||||||
|
import {
|
||||||
|
SanitizedGithub,
|
||||||
|
SanitizedSocial,
|
||||||
|
} from '../../interfaces/sanitized-config';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
profile: Profile | null;
|
||||||
|
loading: boolean;
|
||||||
|
social: SanitizedSocial;
|
||||||
|
github: SanitizedGithub;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isCompanyMention = (company: string): boolean => {
|
||||||
|
return company.startsWith('@') && !company.includes(' ');
|
||||||
|
};
|
||||||
|
|
||||||
|
const companyLink = (company: string): string => {
|
||||||
|
return `https://github.com/${company.substring(1)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFormattedMastodonValue = (
|
||||||
|
mastodonValue: string,
|
||||||
|
isLink: boolean,
|
||||||
|
): string => {
|
||||||
|
const [username, server] = mastodonValue.split('@');
|
||||||
|
|
||||||
|
if (isLink) {
|
||||||
|
return `https://${server}/@${username}`;
|
||||||
|
} else {
|
||||||
|
return `${username}@${server}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ListItem: React.FC<{
|
||||||
|
icon: React.ReactNode;
|
||||||
|
title: React.ReactNode;
|
||||||
|
value: React.ReactNode;
|
||||||
|
link?: string;
|
||||||
|
skeleton?: boolean;
|
||||||
|
}> = ({ icon, title, value, link, skeleton = false }) => {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={link}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="flex justify-start py-2 px-1 items-center"
|
||||||
|
>
|
||||||
|
<div className="flex-grow font-medium gap-2 flex items-center my-1">
|
||||||
|
{icon} {title}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
skeleton ? 'flex-grow' : ''
|
||||||
|
} text-sm font-normal text-right mr-2 ml-3 ${link ? 'truncate' : ''}`}
|
||||||
|
style={{
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the details card component.
|
||||||
|
*
|
||||||
|
* @param {Object} profile - The profile object.
|
||||||
|
* @param {boolean} loading - Indicates whether the data is loading.
|
||||||
|
* @param {Object} social - The social object.
|
||||||
|
* @param {Object} github - The GitHub object.
|
||||||
|
* @return {JSX.Element} The details card component.
|
||||||
|
*/
|
||||||
|
const DetailsCard = ({ profile, loading, social, github }: Props) => {
|
||||||
|
const renderSkeleton = () => {
|
||||||
|
const array = [];
|
||||||
|
for (let index = 0; index < 4; index++) {
|
||||||
|
array.push(
|
||||||
|
<ListItem
|
||||||
|
key={index}
|
||||||
|
skeleton={true}
|
||||||
|
icon={skeleton({ widthCls: 'w-4', heightCls: 'h-4' })}
|
||||||
|
title={skeleton({ widthCls: 'w-24', heightCls: 'h-4' })}
|
||||||
|
value={skeleton({ widthCls: 'w-full', heightCls: '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 />}
|
||||||
|
title="Based in:"
|
||||||
|
value={profile.location}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{profile.company && (
|
||||||
|
<ListItem
|
||||||
|
icon={<FaBuilding />}
|
||||||
|
title="Company:"
|
||||||
|
value={profile.company}
|
||||||
|
link={
|
||||||
|
isCompanyMention(profile.company.trim())
|
||||||
|
? companyLink(profile.company.trim())
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<ListItem
|
||||||
|
icon={<AiFillGithub />}
|
||||||
|
title="GitHub:"
|
||||||
|
value={github.username}
|
||||||
|
link={`https://github.com/${github.username}`}
|
||||||
|
/>
|
||||||
|
{social?.twitter && (
|
||||||
|
<ListItem
|
||||||
|
icon={<SiTwitter />}
|
||||||
|
title="Twitter:"
|
||||||
|
value={social.twitter}
|
||||||
|
link={`https://twitter.com/${social.twitter}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{social?.mastodon && (
|
||||||
|
<ListItem
|
||||||
|
icon={<FaMastodon />}
|
||||||
|
title="Mastodon:"
|
||||||
|
value={getFormattedMastodonValue(social.mastodon, false)}
|
||||||
|
link={getFormattedMastodonValue(social.mastodon, true)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{social?.linkedin && (
|
||||||
|
<ListItem
|
||||||
|
icon={<FaLinkedin />}
|
||||||
|
title="LinkedIn:"
|
||||||
|
value={social.linkedin}
|
||||||
|
link={`https://www.linkedin.com/in/${social.linkedin}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{social?.dribbble && (
|
||||||
|
<ListItem
|
||||||
|
icon={<CgDribbble />}
|
||||||
|
title="Dribbble:"
|
||||||
|
value={social.dribbble}
|
||||||
|
link={`https://dribbble.com/${social.dribbble}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{social?.behance && (
|
||||||
|
<ListItem
|
||||||
|
icon={<FaBehanceSquare />}
|
||||||
|
title="Behance:"
|
||||||
|
value={social.behance}
|
||||||
|
link={`https://www.behance.net/${social.behance}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{social?.facebook && (
|
||||||
|
<ListItem
|
||||||
|
icon={<FaFacebook />}
|
||||||
|
title="Facebook:"
|
||||||
|
value={social.facebook}
|
||||||
|
link={`https://www.facebook.com/${social.facebook}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{social?.instagram && (
|
||||||
|
<ListItem
|
||||||
|
icon={<AiFillInstagram />}
|
||||||
|
title="Instagram:"
|
||||||
|
value={social.instagram}
|
||||||
|
link={`https://www.instagram.com/${social.instagram}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{social?.youtube && (
|
||||||
|
<ListItem
|
||||||
|
icon={<FaYoutube />}
|
||||||
|
title="YouTube:"
|
||||||
|
value={`@${social.youtube}`}
|
||||||
|
link={`https://www.youtube.com/@${social.youtube}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{social?.medium && (
|
||||||
|
<ListItem
|
||||||
|
icon={<AiFillMediumSquare />}
|
||||||
|
title="Medium:"
|
||||||
|
value={social.medium}
|
||||||
|
link={`https://medium.com/@${social.medium}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{social?.dev && (
|
||||||
|
<ListItem
|
||||||
|
icon={<FaDev />}
|
||||||
|
title="Dev:"
|
||||||
|
value={social.dev}
|
||||||
|
link={`https://dev.to/${social.dev}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{social?.stackoverflow && (
|
||||||
|
<ListItem
|
||||||
|
icon={<FaStackOverflow />}
|
||||||
|
title="Stack Overflow:"
|
||||||
|
value={social.stackoverflow.split('/').slice(-1)}
|
||||||
|
link={`https://stackoverflow.com/users/${social.stackoverflow}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{social?.website && (
|
||||||
|
<ListItem
|
||||||
|
icon={<FaGlobe />}
|
||||||
|
title="Website:"
|
||||||
|
value={social.website
|
||||||
|
.replace('https://', '')
|
||||||
|
.replace('http://', '')}
|
||||||
|
link={
|
||||||
|
!social.website.startsWith('http')
|
||||||
|
? `http://${social.website}`
|
||||||
|
: social.website
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{social?.skype && (
|
||||||
|
<ListItem
|
||||||
|
icon={<FaSkype />}
|
||||||
|
title="Skype"
|
||||||
|
value={social.skype}
|
||||||
|
link={`skype:${social.skype}?chat`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{social?.telegram && (
|
||||||
|
<ListItem
|
||||||
|
icon={<FaTelegram />}
|
||||||
|
title="Telegram"
|
||||||
|
value={social.telegram}
|
||||||
|
link={`https://t.me/${social.telegram}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{social?.phone && (
|
||||||
|
<ListItem
|
||||||
|
icon={<RiPhoneFill />}
|
||||||
|
title="Phone:"
|
||||||
|
value={social.phone}
|
||||||
|
link={`tel:${social.phone}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{social?.email && (
|
||||||
|
<ListItem
|
||||||
|
icon={<RiMailFill />}
|
||||||
|
title="Email:"
|
||||||
|
value={social.email}
|
||||||
|
link={`mailto:${social.email}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DetailsCard;
|
||||||
90
src/components/education-card/index.tsx
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { SanitizedEducation } from '../../interfaces/sanitized-config';
|
||||||
|
import { skeleton } from '../../utils';
|
||||||
|
|
||||||
|
const ListItem = ({
|
||||||
|
time,
|
||||||
|
degree,
|
||||||
|
institution,
|
||||||
|
}: {
|
||||||
|
time: React.ReactNode;
|
||||||
|
degree?: React.ReactNode;
|
||||||
|
institution?: React.ReactNode;
|
||||||
|
}) => (
|
||||||
|
<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 EducationCard = ({
|
||||||
|
loading,
|
||||||
|
educations,
|
||||||
|
}: {
|
||||||
|
loading: boolean;
|
||||||
|
educations: SanitizedEducation[];
|
||||||
|
}) => {
|
||||||
|
const renderSkeleton = () => {
|
||||||
|
const array = [];
|
||||||
|
for (let index = 0; index < 2; index++) {
|
||||||
|
array.push(
|
||||||
|
<ListItem
|
||||||
|
key={index}
|
||||||
|
time={skeleton({
|
||||||
|
widthCls: 'w-5/12',
|
||||||
|
heightCls: 'h-4',
|
||||||
|
})}
|
||||||
|
degree={skeleton({
|
||||||
|
widthCls: 'w-6/12',
|
||||||
|
heightCls: 'h-4',
|
||||||
|
className: 'my-1.5',
|
||||||
|
})}
|
||||||
|
institution={skeleton({ widthCls: 'w-6/12', heightCls: 'h-3' })}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card shadow-lg compact bg-base-100">
|
||||||
|
<div className="card-body">
|
||||||
|
<div className="mx-3">
|
||||||
|
<h5 className="card-title">
|
||||||
|
{loading ? (
|
||||||
|
skeleton({ widthCls: 'w-32', heightCls: 'h-8' })
|
||||||
|
) : (
|
||||||
|
<span className="text-base-content 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()
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{educations.map((item, index) => (
|
||||||
|
<ListItem
|
||||||
|
key={index}
|
||||||
|
time={`${item.from} - ${item.to}`}
|
||||||
|
degree={item.degree}
|
||||||
|
institution={item.institution}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EducationCard;
|
||||||
31
src/components/error-page/index.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { CustomError } from '../../constants/errors';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the ErrorPage component.
|
||||||
|
*
|
||||||
|
* @param props - The props for the ErrorPage component.
|
||||||
|
* @returns The rendered ErrorPage component.
|
||||||
|
*/
|
||||||
|
const ErrorPage: React.FC<CustomError> = (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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ErrorPage;
|
||||||
100
src/components/experience-card/index.tsx
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import { SanitizedExperience } from '../../interfaces/sanitized-config';
|
||||||
|
import { skeleton } from '../../utils';
|
||||||
|
|
||||||
|
const ListItem = ({
|
||||||
|
time,
|
||||||
|
position,
|
||||||
|
company,
|
||||||
|
companyLink,
|
||||||
|
}: {
|
||||||
|
time: React.ReactNode;
|
||||||
|
position?: React.ReactNode;
|
||||||
|
company?: React.ReactNode;
|
||||||
|
companyLink?: string;
|
||||||
|
}) => (
|
||||||
|
<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">
|
||||||
|
<a href={companyLink} target="_blank" rel="noreferrer">
|
||||||
|
{company}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ExperienceCard = ({
|
||||||
|
experiences,
|
||||||
|
loading,
|
||||||
|
}: {
|
||||||
|
experiences: SanitizedExperience[];
|
||||||
|
loading: boolean;
|
||||||
|
}) => {
|
||||||
|
const renderSkeleton = () => {
|
||||||
|
const array = [];
|
||||||
|
for (let index = 0; index < 2; index++) {
|
||||||
|
array.push(
|
||||||
|
<ListItem
|
||||||
|
key={index}
|
||||||
|
time={skeleton({
|
||||||
|
widthCls: 'w-5/12',
|
||||||
|
heightCls: 'h-4',
|
||||||
|
})}
|
||||||
|
position={skeleton({
|
||||||
|
widthCls: 'w-6/12',
|
||||||
|
heightCls: 'h-4',
|
||||||
|
className: 'my-1.5',
|
||||||
|
})}
|
||||||
|
company={skeleton({ widthCls: 'w-6/12', heightCls: 'h-3' })}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="card shadow-lg compact bg-base-100">
|
||||||
|
<div className="card-body">
|
||||||
|
<div className="mx-3">
|
||||||
|
<h5 className="card-title">
|
||||||
|
{loading ? (
|
||||||
|
skeleton({ widthCls: 'w-32', heightCls: 'h-8' })
|
||||||
|
) : (
|
||||||
|
<span className="text-base-content 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}
|
||||||
|
companyLink={
|
||||||
|
experience.companyLink
|
||||||
|
? experience.companyLink
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExperienceCard;
|
||||||
158
src/components/external-project-card/index.tsx
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import { Fragment } from 'react';
|
||||||
|
import LazyImage from '../lazy-image';
|
||||||
|
import { ga, skeleton } from '../../utils';
|
||||||
|
import { SanitizedExternalProject } from '../../interfaces/sanitized-config';
|
||||||
|
|
||||||
|
const ExternalProjectCard = ({
|
||||||
|
externalProjects,
|
||||||
|
header,
|
||||||
|
loading,
|
||||||
|
googleAnalyticId,
|
||||||
|
}: {
|
||||||
|
externalProjects: SanitizedExternalProject[];
|
||||||
|
header: string;
|
||||||
|
loading: boolean;
|
||||||
|
googleAnalyticId?: string;
|
||||||
|
}) => {
|
||||||
|
const renderSkeleton = () => {
|
||||||
|
const array = [];
|
||||||
|
for (let index = 0; index < externalProjects.length; 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">
|
||||||
|
<div className="w-full">
|
||||||
|
<div className="flex items-start px-4">
|
||||||
|
<div className="w-full">
|
||||||
|
<h2>
|
||||||
|
{skeleton({
|
||||||
|
widthCls: 'w-32',
|
||||||
|
heightCls: 'h-8',
|
||||||
|
className: 'mb-2 mx-auto',
|
||||||
|
})}
|
||||||
|
</h2>
|
||||||
|
<div className="avatar w-full h-full">
|
||||||
|
<div className="w-20 h-20 mask mask-squircle mx-auto">
|
||||||
|
{skeleton({
|
||||||
|
widthCls: 'w-full',
|
||||||
|
heightCls: 'h-full',
|
||||||
|
shape: '',
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2">
|
||||||
|
{skeleton({
|
||||||
|
widthCls: 'w-full',
|
||||||
|
heightCls: 'h-4',
|
||||||
|
className: 'mx-auto',
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 flex items-center flex-wrap justify-center">
|
||||||
|
{skeleton({
|
||||||
|
widthCls: 'w-full',
|
||||||
|
heightCls: 'h-4',
|
||||||
|
className: 'mx-auto',
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderExternalProjects = () => {
|
||||||
|
return externalProjects.map((item, index) => (
|
||||||
|
<a
|
||||||
|
className="card shadow-lg compact bg-base-100 cursor-pointer"
|
||||||
|
key={index}
|
||||||
|
href={item.link}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (googleAnalyticId) {
|
||||||
|
ga.event('Click External Project', {
|
||||||
|
post: item.title,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
window?.open(item.link, '_blank');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="p-8 h-full w-full">
|
||||||
|
<div className="flex items-center flex-col">
|
||||||
|
<div className="w-full">
|
||||||
|
<div className="px-4">
|
||||||
|
<div className="text-center w-full">
|
||||||
|
<h2 className="font-semibold text-lg tracking-wide text-center opacity-60 mb-2">
|
||||||
|
{item.title}
|
||||||
|
</h2>
|
||||||
|
{item.imageUrl && (
|
||||||
|
<div className="avatar opacity-90">
|
||||||
|
<div className="w-20 h-20 mask mask-squircle">
|
||||||
|
<LazyImage
|
||||||
|
src={item.imageUrl}
|
||||||
|
alt={'thumbnail'}
|
||||||
|
placeholder={skeleton({
|
||||||
|
widthCls: 'w-full',
|
||||||
|
heightCls: 'h-full',
|
||||||
|
shape: '',
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<p className="mt-1 text-base-content text-opacity-60 text-sm">
|
||||||
|
{item.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</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-base-100 shadow bg-opacity-40">
|
||||||
|
<div className="card-body">
|
||||||
|
<div className="mx-3 flex items-center justify-between mb-2">
|
||||||
|
<h5 className="card-title">
|
||||||
|
{loading ? (
|
||||||
|
skeleton({ widthCls: 'w-40', heightCls: 'h-8' })
|
||||||
|
) : (
|
||||||
|
<span className="text-base-content opacity-70">
|
||||||
|
{header}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-2">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
{loading ? renderSkeleton() : renderExternalProjects()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExternalProjectCard;
|
||||||
23
src/components/footer/index.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { skeleton } from '../../utils';
|
||||||
|
|
||||||
|
const Footer = ({
|
||||||
|
content,
|
||||||
|
loading,
|
||||||
|
}: {
|
||||||
|
content: string | null;
|
||||||
|
loading: boolean;
|
||||||
|
}) => {
|
||||||
|
if (!content) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card-body">
|
||||||
|
{loading ? (
|
||||||
|
skeleton({ widthCls: 'w-52', heightCls: 'h-6' })
|
||||||
|
) : (
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: content }} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Footer;
|
||||||
180
src/components/github-project-card/index.tsx
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
import { Fragment } from 'react';
|
||||||
|
import { AiOutlineFork, AiOutlineStar } from 'react-icons/ai';
|
||||||
|
import { MdInsertLink } from 'react-icons/md';
|
||||||
|
import { ga, getLanguageColor, skeleton } from '../../utils';
|
||||||
|
import { GithubProject } from '../../interfaces/github-project';
|
||||||
|
|
||||||
|
const GithubProjectCard = ({
|
||||||
|
header,
|
||||||
|
githubProjects,
|
||||||
|
loading,
|
||||||
|
limit,
|
||||||
|
username,
|
||||||
|
googleAnalyticsId,
|
||||||
|
}: {
|
||||||
|
header: string;
|
||||||
|
githubProjects: GithubProject[];
|
||||||
|
loading: boolean;
|
||||||
|
limit: number;
|
||||||
|
username: string;
|
||||||
|
googleAnalyticsId?: string;
|
||||||
|
}) => {
|
||||||
|
if (!loading && githubProjects.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderSkeleton = () => {
|
||||||
|
const array = [];
|
||||||
|
for (let index = 0; index < 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({
|
||||||
|
widthCls: 'w-32',
|
||||||
|
heightCls: 'h-8',
|
||||||
|
className: 'mb-1',
|
||||||
|
})}
|
||||||
|
</h5>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="mb-5 mt-1">
|
||||||
|
{skeleton({
|
||||||
|
widthCls: 'w-full',
|
||||||
|
heightCls: 'h-4',
|
||||||
|
className: 'mb-2',
|
||||||
|
})}
|
||||||
|
{skeleton({ widthCls: 'w-full', heightCls: 'h-4' })}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<div className="flex flex-grow">
|
||||||
|
<span className="mr-3 flex items-center">
|
||||||
|
{skeleton({ widthCls: 'w-12', heightCls: 'h-4' })}
|
||||||
|
</span>
|
||||||
|
<span className="flex items-center">
|
||||||
|
{skeleton({ widthCls: 'w-12', heightCls: 'h-4' })}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="flex items-center">
|
||||||
|
{skeleton({ widthCls: 'w-12', heightCls: 'h-4' })}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderProjects = () => {
|
||||||
|
return githubProjects.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 (googleAnalyticsId) {
|
||||||
|
ga.event('Click project', {
|
||||||
|
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">
|
||||||
|
<div className="card-title text-lg tracking-wide flex text-base-content opacity-60">
|
||||||
|
<MdInsertLink className="my-auto" />
|
||||||
|
<span>{item.name}</span>
|
||||||
|
</div>
|
||||||
|
</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 truncate">
|
||||||
|
<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: getLanguageColor(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-base-100 shadow bg-opacity-40">
|
||||||
|
<div className="card-body">
|
||||||
|
<div className="mx-3 flex items-center justify-between mb-2">
|
||||||
|
<h5 className="card-title">
|
||||||
|
{loading ? (
|
||||||
|
skeleton({ widthCls: 'w-40', heightCls: 'h-8' })
|
||||||
|
) : (
|
||||||
|
<span className="text-base-content opacity-70">
|
||||||
|
{header}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</h5>
|
||||||
|
{loading ? (
|
||||||
|
skeleton({ widthCls: 'w-10', heightCls: 'h-5' })
|
||||||
|
) : (
|
||||||
|
<a
|
||||||
|
href={`https://github.com/${username}?tab=repositories`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-base-content opacity-50 hover:underline"
|
||||||
|
>
|
||||||
|
See All
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="col-span-2">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
{loading ? renderSkeleton() : renderProjects()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GithubProjectCard;
|
||||||
296
src/components/gitprofile.tsx
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import axios, { AxiosError } from 'axios';
|
||||||
|
import { formatDistance } from 'date-fns';
|
||||||
|
import {
|
||||||
|
CustomError,
|
||||||
|
GENERIC_ERROR,
|
||||||
|
INVALID_CONFIG_ERROR,
|
||||||
|
INVALID_GITHUB_USERNAME_ERROR,
|
||||||
|
setTooManyRequestError,
|
||||||
|
} from '../constants/errors';
|
||||||
|
import { HelmetProvider } from 'react-helmet-async';
|
||||||
|
import '../assets/index.css';
|
||||||
|
import { getInitialTheme, getSanitizedConfig, setupHotjar } from '../utils';
|
||||||
|
import { SanitizedConfig } from '../interfaces/sanitized-config';
|
||||||
|
import ErrorPage from './error-page';
|
||||||
|
import HeadTagEditor from './head-tag-editor';
|
||||||
|
import { DEFAULT_THEMES } from '../constants/default-themes';
|
||||||
|
import ThemeChanger from './theme-changer';
|
||||||
|
import { BG_COLOR } from '../constants';
|
||||||
|
import AvatarCard from './avatar-card';
|
||||||
|
import { Profile } from '../interfaces/profile';
|
||||||
|
import DetailsCard from './details-card';
|
||||||
|
import SkillCard from './skill-card';
|
||||||
|
import ExperienceCard from './experience-card';
|
||||||
|
import EducationCard from './education-card';
|
||||||
|
import CertificationCard from './certification-card';
|
||||||
|
import { GithubProject } from '../interfaces/github-project';
|
||||||
|
import GithubProjectCard from './github-project-card';
|
||||||
|
import ExternalProjectCard from './external-project-card';
|
||||||
|
import BlogCard from './blog-card';
|
||||||
|
import Footer from './footer';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the GitProfile component.
|
||||||
|
*
|
||||||
|
* @param {Object} config - the configuration object
|
||||||
|
* @return {JSX.Element} the rendered GitProfile component
|
||||||
|
*/
|
||||||
|
const GitProfile = ({ config }: { config: Config }) => {
|
||||||
|
const [sanitizedConfig] = useState<SanitizedConfig | Record<string, never>>(
|
||||||
|
getSanitizedConfig(config),
|
||||||
|
);
|
||||||
|
const [theme, setTheme] = useState<string>(DEFAULT_THEMES[0]);
|
||||||
|
const [error, setError] = useState<CustomError | null>(null);
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [profile, setProfile] = useState<Profile | null>(null);
|
||||||
|
const [githubProjects, setGithubProjects] = useState<GithubProject[]>([]);
|
||||||
|
|
||||||
|
const getGithubProjects = useCallback(
|
||||||
|
async (publicRepoCount: number): Promise<GithubProject[]> => {
|
||||||
|
if (sanitizedConfig.projects.github.mode === 'automatic') {
|
||||||
|
if (publicRepoCount === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const excludeRepo =
|
||||||
|
sanitizedConfig.projects.github.automatic.exclude.projects
|
||||||
|
.map((project) => `+-repo:${project}`)
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
const query = `user:${sanitizedConfig.github.username}+fork:${!sanitizedConfig.projects.github.automatic.exclude.forks}${excludeRepo}`;
|
||||||
|
const url = `https://api.github.com/search/repositories?q=${query}&sort=${sanitizedConfig.projects.github.automatic.sortBy}&per_page=${sanitizedConfig.projects.github.automatic.limit}&type=Repositories`;
|
||||||
|
|
||||||
|
const repoResponse = await axios.get(url, {
|
||||||
|
headers: { 'Content-Type': 'application/vnd.github.v3+json' },
|
||||||
|
});
|
||||||
|
const repoData = repoResponse.data;
|
||||||
|
|
||||||
|
return repoData.items;
|
||||||
|
} else {
|
||||||
|
if (sanitizedConfig.projects.github.manual.projects.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const repos = sanitizedConfig.projects.github.manual.projects
|
||||||
|
.map((project) => `+repo:${project}`)
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
const url = `https://api.github.com/search/repositories?q=${repos}&type=Repositories`;
|
||||||
|
|
||||||
|
const repoResponse = await axios.get(url, {
|
||||||
|
headers: { 'Content-Type': 'application/vnd.github.v3+json' },
|
||||||
|
});
|
||||||
|
const repoData = repoResponse.data;
|
||||||
|
|
||||||
|
return repoData.items;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
sanitizedConfig.github.username,
|
||||||
|
sanitizedConfig.projects.github.mode,
|
||||||
|
sanitizedConfig.projects.github.manual.projects,
|
||||||
|
sanitizedConfig.projects.github.automatic.sortBy,
|
||||||
|
sanitizedConfig.projects.github.automatic.limit,
|
||||||
|
sanitizedConfig.projects.github.automatic.exclude.forks,
|
||||||
|
sanitizedConfig.projects.github.automatic.exclude.projects,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadData = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
const response = await axios.get(
|
||||||
|
`https://api.github.com/users/${sanitizedConfig.github.username}`,
|
||||||
|
);
|
||||||
|
const data = response.data;
|
||||||
|
|
||||||
|
setProfile({
|
||||||
|
avatar: data.avatar_url,
|
||||||
|
name: data.name || ' ',
|
||||||
|
bio: data.bio || '',
|
||||||
|
location: data.location || '',
|
||||||
|
company: data.company || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!sanitizedConfig.projects.github.display) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setGithubProjects(await getGithubProjects(data.public_repos));
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error as AxiosError | Error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
sanitizedConfig.github.username,
|
||||||
|
sanitizedConfig.projects.github.display,
|
||||||
|
getGithubProjects,
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (Object.keys(sanitizedConfig).length === 0) {
|
||||||
|
setError(INVALID_CONFIG_ERROR);
|
||||||
|
} else {
|
||||||
|
setError(null);
|
||||||
|
setTheme(getInitialTheme(sanitizedConfig.themeConfig));
|
||||||
|
setupHotjar(sanitizedConfig.hotjar);
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
}, [sanitizedConfig, loadData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
theme && document.documentElement.setAttribute('data-theme', theme);
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
|
const handleError = (error: AxiosError | Error): void => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
|
||||||
|
if (error instanceof AxiosError) {
|
||||||
|
try {
|
||||||
|
const reset = formatDistance(
|
||||||
|
new Date(error.response?.headers?.['x-ratelimit-reset'] * 1000),
|
||||||
|
new Date(),
|
||||||
|
{ addSuffix: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (typeof error.response?.status === 'number') {
|
||||||
|
switch (error.response.status) {
|
||||||
|
case 403:
|
||||||
|
setError(setTooManyRequestError(reset));
|
||||||
|
break;
|
||||||
|
case 404:
|
||||||
|
setError(INVALID_GITHUB_USERNAME_ERROR);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
setError(GENERIC_ERROR);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setError(GENERIC_ERROR);
|
||||||
|
}
|
||||||
|
} catch (innerError) {
|
||||||
|
setError(GENERIC_ERROR);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setError(GENERIC_ERROR);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HelmetProvider>
|
||||||
|
<div className="fade-in h-screen">
|
||||||
|
{error ? (
|
||||||
|
<ErrorPage
|
||||||
|
status={error.status}
|
||||||
|
title={error.title}
|
||||||
|
subTitle={error.subTitle}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<HeadTagEditor
|
||||||
|
googleAnalyticsId={sanitizedConfig.googleAnalytics.id}
|
||||||
|
appliedTheme={theme}
|
||||||
|
/>
|
||||||
|
<div className={`p-4 lg:p-10 min-h-full ${BG_COLOR}`}>
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 rounded-box">
|
||||||
|
<div className="col-span-1">
|
||||||
|
<div className="grid grid-cols-1 gap-6">
|
||||||
|
{!sanitizedConfig.themeConfig.disableSwitch && (
|
||||||
|
<ThemeChanger
|
||||||
|
theme={theme}
|
||||||
|
setTheme={setTheme}
|
||||||
|
loading={loading}
|
||||||
|
themeConfig={sanitizedConfig.themeConfig}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<AvatarCard
|
||||||
|
profile={profile}
|
||||||
|
loading={loading}
|
||||||
|
avatarRing={sanitizedConfig.themeConfig.displayAvatarRing}
|
||||||
|
resumeFileUrl={sanitizedConfig.resume.fileUrl}
|
||||||
|
/>
|
||||||
|
<DetailsCard
|
||||||
|
profile={profile}
|
||||||
|
loading={loading}
|
||||||
|
github={sanitizedConfig.github}
|
||||||
|
social={sanitizedConfig.social}
|
||||||
|
/>
|
||||||
|
{sanitizedConfig.skills.length !== 0 && (
|
||||||
|
<SkillCard
|
||||||
|
loading={loading}
|
||||||
|
skills={sanitizedConfig.skills}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{sanitizedConfig.experiences.length !== 0 && (
|
||||||
|
<ExperienceCard
|
||||||
|
loading={loading}
|
||||||
|
experiences={sanitizedConfig.experiences}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{sanitizedConfig.certifications.length !== 0 && (
|
||||||
|
<CertificationCard
|
||||||
|
loading={loading}
|
||||||
|
certifications={sanitizedConfig.certifications}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{sanitizedConfig.educations.length !== 0 && (
|
||||||
|
<EducationCard
|
||||||
|
loading={loading}
|
||||||
|
educations={sanitizedConfig.educations}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="lg:col-span-2 col-span-1">
|
||||||
|
<div className="grid grid-cols-1 gap-6">
|
||||||
|
{sanitizedConfig.projects.github.display && (
|
||||||
|
<GithubProjectCard
|
||||||
|
header={sanitizedConfig.projects.github.header}
|
||||||
|
limit={sanitizedConfig.projects.github.automatic.limit}
|
||||||
|
githubProjects={githubProjects}
|
||||||
|
loading={loading}
|
||||||
|
username={sanitizedConfig.github.username}
|
||||||
|
googleAnalyticsId={sanitizedConfig.googleAnalytics.id}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{sanitizedConfig.projects.external.projects.length !==
|
||||||
|
0 && (
|
||||||
|
<ExternalProjectCard
|
||||||
|
loading={loading}
|
||||||
|
header={sanitizedConfig.projects.external.header}
|
||||||
|
externalProjects={
|
||||||
|
sanitizedConfig.projects.external.projects
|
||||||
|
}
|
||||||
|
googleAnalyticId={sanitizedConfig.googleAnalytics.id}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{sanitizedConfig.blog.display && (
|
||||||
|
<BlogCard
|
||||||
|
loading={loading}
|
||||||
|
googleAnalyticsId={sanitizedConfig.googleAnalytics.id}
|
||||||
|
blog={sanitizedConfig.blog}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{sanitizedConfig.footer && (
|
||||||
|
<footer
|
||||||
|
className={`p-4 footer ${BG_COLOR} text-base-content footer-center`}
|
||||||
|
>
|
||||||
|
<div className="card compact bg-base-100 shadow">
|
||||||
|
<Footer content={sanitizedConfig.footer} loading={loading} />
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</HelmetProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GitProfile;
|
||||||
47
src/components/head-tag-editor/index.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { Helmet } from 'react-helmet-async';
|
||||||
|
import { isDarkishTheme } from '../../utils';
|
||||||
|
|
||||||
|
type HeadTagEditorProps = {
|
||||||
|
googleAnalyticsId?: string;
|
||||||
|
appliedTheme: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the head tag editor component.
|
||||||
|
*
|
||||||
|
* @param {HeadTagEditorProps} googleAnalyticsId - The Google Analytics ID.
|
||||||
|
* @param {HeadTagEditorProps} appliedTheme - The applied theme.
|
||||||
|
* @return {React.ReactElement} The head tag editor component.
|
||||||
|
*/
|
||||||
|
const HeadTagEditor: React.FC<HeadTagEditorProps> = ({
|
||||||
|
googleAnalyticsId,
|
||||||
|
appliedTheme,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Helmet>
|
||||||
|
<meta
|
||||||
|
name="theme-color"
|
||||||
|
content={isDarkishTheme(appliedTheme) ? '#000000' : '#ffffff'}
|
||||||
|
/>
|
||||||
|
{googleAnalyticsId && (
|
||||||
|
<>
|
||||||
|
<script
|
||||||
|
async
|
||||||
|
src={`https://www.googletagmanager.com/gtag/js?id=${googleAnalyticsId}`}
|
||||||
|
></script>
|
||||||
|
<script>
|
||||||
|
{`window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag() {
|
||||||
|
dataLayer.push(arguments);
|
||||||
|
}
|
||||||
|
gtag('js', new Date());
|
||||||
|
gtag('config', '${googleAnalyticsId}');
|
||||||
|
`}
|
||||||
|
</script>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Helmet>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HeadTagEditor;
|
||||||
38
src/components/lazy-image/index.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { useState, Fragment, useEffect } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LazyImage component.
|
||||||
|
*
|
||||||
|
* @param {string} placeholder The placeholder image URL.
|
||||||
|
* @param {string} src The image URL.
|
||||||
|
* @param {string} alt The alt text for the image.
|
||||||
|
* @param {object} rest Additional props for the image element.
|
||||||
|
*
|
||||||
|
* @returns {ReactElement} The LazyImage component.
|
||||||
|
*/
|
||||||
|
const LazyImage: React.FC<{
|
||||||
|
placeholder: React.ReactElement;
|
||||||
|
src: string;
|
||||||
|
alt: string;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
[key: string]: any;
|
||||||
|
}> = ({ placeholder, src, alt, ...rest }): React.ReactElement => {
|
||||||
|
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;
|
||||||
54
src/components/skill-card/index.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { skeleton } from '../../utils';
|
||||||
|
|
||||||
|
const SkillCard = ({
|
||||||
|
loading,
|
||||||
|
skills,
|
||||||
|
}: {
|
||||||
|
loading: boolean;
|
||||||
|
skills: string[];
|
||||||
|
}) => {
|
||||||
|
const renderSkeleton = () => {
|
||||||
|
const array = [];
|
||||||
|
for (let index = 0; index < 12; index++) {
|
||||||
|
array.push(
|
||||||
|
<div key={index}>
|
||||||
|
{skeleton({ widthCls: 'w-16', heightCls: 'h-4', className: 'm-1' })}
|
||||||
|
</div>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card shadow-lg compact bg-base-100">
|
||||||
|
<div className="card-body">
|
||||||
|
<div className="mx-3">
|
||||||
|
<h5 className="card-title">
|
||||||
|
{loading ? (
|
||||||
|
skeleton({ widthCls: 'w-32', heightCls: 'h-8' })
|
||||||
|
) : (
|
||||||
|
<span className="text-base-content 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SkillCard;
|
||||||
121
src/components/theme-changer/index.tsx
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import { AiOutlineControl } from 'react-icons/ai';
|
||||||
|
import { SanitizedThemeConfig } from '../../interfaces/sanitized-config';
|
||||||
|
import { LOCAL_STORAGE_KEY_NAME } from '../../constants';
|
||||||
|
import { skeleton } from '../../utils';
|
||||||
|
import { MouseEvent } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a theme changer component.
|
||||||
|
*
|
||||||
|
* @param {Object} props - The props object.
|
||||||
|
* @param {string} props.theme - The current theme.
|
||||||
|
* @param {function} props.setTheme - A function to set the theme.
|
||||||
|
* @param {boolean} props.loading - Whether the component is in a loading state.
|
||||||
|
* @param {SanitizedThemeConfig} props.themeConfig - The theme configuration object.
|
||||||
|
* @return {JSX.Element} The rendered theme changer component.
|
||||||
|
*/
|
||||||
|
const ThemeChanger = ({
|
||||||
|
theme,
|
||||||
|
setTheme,
|
||||||
|
loading,
|
||||||
|
themeConfig,
|
||||||
|
}: {
|
||||||
|
theme: string;
|
||||||
|
setTheme: (theme: string) => void;
|
||||||
|
loading: boolean;
|
||||||
|
themeConfig: SanitizedThemeConfig;
|
||||||
|
}) => {
|
||||||
|
const changeTheme = (
|
||||||
|
e: MouseEvent<HTMLAnchorElement>,
|
||||||
|
selectedTheme: string,
|
||||||
|
) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
document.querySelector('html')?.setAttribute('data-theme', selectedTheme);
|
||||||
|
|
||||||
|
typeof window !== 'undefined' &&
|
||||||
|
localStorage.setItem(LOCAL_STORAGE_KEY_NAME, 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({
|
||||||
|
widthCls: 'w-20',
|
||||||
|
heightCls: 'h-8',
|
||||||
|
className: 'mb-1',
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<span className="text-base-content opacity-70">Theme</span>
|
||||||
|
)}
|
||||||
|
</h5>
|
||||||
|
<span className="text-base-content text-opacity-40 capitalize text-sm">
|
||||||
|
{loading
|
||||||
|
? skeleton({ widthCls: 'w-16', heightCls: 'h-5' })
|
||||||
|
: theme === themeConfig.defaultTheme
|
||||||
|
? 'Default'
|
||||||
|
: theme}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex-0">
|
||||||
|
{loading ? (
|
||||||
|
skeleton({
|
||||||
|
widthCls: 'w-14 md:w-28',
|
||||||
|
heightCls: '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 text-base-content"
|
||||||
|
>
|
||||||
|
<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-lg bg-base-200 text-base-content z-10"
|
||||||
|
>
|
||||||
|
<ul className="p-4 menu compact">
|
||||||
|
{[
|
||||||
|
themeConfig.defaultTheme,
|
||||||
|
...themeConfig.themes.filter(
|
||||||
|
(item) => item !== themeConfig.defaultTheme,
|
||||||
|
),
|
||||||
|
].map((item, index) => (
|
||||||
|
<li key={index}>
|
||||||
|
{}
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ThemeChanger;
|
||||||
130
src/config.js
@@ -1,130 +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: [] // These projects will not be displayed. example: ['my-project1', 'my-project2']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
social: {
|
|
||||||
linkedin: 'ariful-alam',
|
|
||||||
twitter: 'arif_swozon',
|
|
||||||
facebook: '',
|
|
||||||
dribbble: '',
|
|
||||||
behance: '',
|
|
||||||
medium: '',
|
|
||||||
devto: '',
|
|
||||||
website: 'https://arifszn.github.io',
|
|
||||||
phone: '',
|
|
||||||
email: 'contact@arifszn.com'
|
|
||||||
},
|
|
||||||
skills: [
|
|
||||||
'PHP',
|
|
||||||
'Laravel',
|
|
||||||
'JavaScript',
|
|
||||||
'React.js',
|
|
||||||
'Vue.js',
|
|
||||||
'Node.js',
|
|
||||||
'Jquery',
|
|
||||||
'MySQL',
|
|
||||||
'Git',
|
|
||||||
'Docker',
|
|
||||||
'CSS',
|
|
||||||
'Antd',
|
|
||||||
'Tailwind',
|
|
||||||
'Bootstrap',
|
|
||||||
],
|
|
||||||
experiences: [
|
|
||||||
{
|
|
||||||
company: 'Monstarlab Bangladesh',
|
|
||||||
position: 'Backend Engineer',
|
|
||||||
from: 'September 2021',
|
|
||||||
to: 'Present'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
company: 'Orangetoolz - My Offer 360 Degree',
|
|
||||||
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: 2 // 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
|
|
||||||
},
|
|
||||||
hotjar: {
|
|
||||||
id: '2617601', // Please remove this and use your own id
|
|
||||||
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'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
9
src/constants/default-custom-theme.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export const DEFAULT_CUSTOM_THEME = {
|
||||||
|
primary: '#fc055b',
|
||||||
|
secondary: '#219aaf',
|
||||||
|
accent: '#e8d03a',
|
||||||
|
neutral: '#2A2730',
|
||||||
|
'base-100': '#E3E3ED',
|
||||||
|
'--rounded-box': '3rem',
|
||||||
|
'--rounded-btn': '3rem',
|
||||||
|
};
|
||||||
35
src/constants/default-themes.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
export const DEFAULT_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',
|
||||||
|
'dim',
|
||||||
|
'nord',
|
||||||
|
'sunset',
|
||||||
|
'procyon',
|
||||||
|
];
|
||||||
55
src/constants/errors.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { ReactElement } from 'react';
|
||||||
|
|
||||||
|
export interface CustomError {
|
||||||
|
status: number;
|
||||||
|
title: string;
|
||||||
|
subTitle: string | ReactElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const INVALID_CONFIG_ERROR: CustomError = {
|
||||||
|
status: 500,
|
||||||
|
title: 'Invalid Config!',
|
||||||
|
subTitle: (
|
||||||
|
<p>
|
||||||
|
Please provide correct config in <code>gitprofile.config.ts</code>.
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setTooManyRequestError = (resetTime: string): CustomError => {
|
||||||
|
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"
|
||||||
|
className="underline"
|
||||||
|
>
|
||||||
|
rate limit
|
||||||
|
</a>
|
||||||
|
! Try again later{` ${resetTime}`}.
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const INVALID_GITHUB_USERNAME_ERROR: CustomError = {
|
||||||
|
status: 404,
|
||||||
|
title: 'Invalid GitHub Username!',
|
||||||
|
subTitle: (
|
||||||
|
<p>
|
||||||
|
Please provide correct github username in{' '}
|
||||||
|
<code>gitprofile.config.ts</code>.
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GENERIC_ERROR: CustomError = {
|
||||||
|
status: 500,
|
||||||
|
title: 'Ops!!',
|
||||||
|
subTitle: 'Something went wrong.',
|
||||||
|
};
|
||||||
6
src/constants/index.tsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export const LOCAL_STORAGE_KEY_NAME = 'gitprofile-theme';
|
||||||
|
|
||||||
|
export const BG_COLOR = 'bg-base-300';
|
||||||
|
|
||||||
|
export const FALLBACK_IMAGE =
|
||||||
|
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg==';
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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
@@ -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('ezprofileTheme')) {
|
|
||||||
let theme = localStorage.getItem('ezprofileTheme');
|
|
||||||
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,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg=="
|
|
||||||
)
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
25
src/index.js
@@ -1,25 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import './index.scss';
|
|
||||||
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();
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
@use "sass:meta";
|
|
||||||
|
|
||||||
::-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;
|
|
||||||
--z-primary: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
|
||||||
monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
.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; }
|
|
||||||
}
|
|
||||||
8
src/interfaces/article.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export interface Article {
|
||||||
|
title: string;
|
||||||
|
thumbnail: string;
|
||||||
|
link: string;
|
||||||
|
publishedAt: Date;
|
||||||
|
description: string;
|
||||||
|
categories: string[];
|
||||||
|
}
|
||||||
8
src/interfaces/github-project.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export interface GithubProject {
|
||||||
|
name: string;
|
||||||
|
html_url: string;
|
||||||
|
description: string;
|
||||||
|
stargazers_count: string;
|
||||||
|
forks_count: string;
|
||||||
|
language: string;
|
||||||
|
}
|
||||||
7
src/interfaces/profile.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export interface Profile {
|
||||||
|
avatar: string;
|
||||||
|
name: string;
|
||||||
|
bio?: string;
|
||||||
|
location?: string;
|
||||||
|
company?: string;
|
||||||
|
}
|
||||||
141
src/interfaces/sanitized-config.tsx
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
export interface SanitizedGithub {
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SanitizedGitHubProjects {
|
||||||
|
display: boolean;
|
||||||
|
header: string;
|
||||||
|
mode: string;
|
||||||
|
automatic: {
|
||||||
|
sortBy: string;
|
||||||
|
limit: number;
|
||||||
|
exclude: {
|
||||||
|
forks: boolean;
|
||||||
|
projects: Array<string>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
manual: {
|
||||||
|
projects: Array<string>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SanitizedExternalProject {
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
imageUrl?: string;
|
||||||
|
link: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SanitizedExternalProjects {
|
||||||
|
header: string;
|
||||||
|
projects: SanitizedExternalProject[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SanitizedProjects {
|
||||||
|
github: SanitizedGitHubProjects;
|
||||||
|
external: SanitizedExternalProjects;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SanitizedSEO {
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
imageURL?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SanitizedSocial {
|
||||||
|
linkedin?: string;
|
||||||
|
twitter?: string;
|
||||||
|
mastodon?: string;
|
||||||
|
facebook?: string;
|
||||||
|
instagram?: string;
|
||||||
|
youtube?: string;
|
||||||
|
dribbble?: string;
|
||||||
|
behance?: string;
|
||||||
|
medium?: string;
|
||||||
|
dev?: string;
|
||||||
|
stackoverflow?: string;
|
||||||
|
website?: string;
|
||||||
|
skype?: string;
|
||||||
|
telegram?: string;
|
||||||
|
phone?: string;
|
||||||
|
email?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SanitizedResume {
|
||||||
|
fileUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SanitizedExperience {
|
||||||
|
company?: string;
|
||||||
|
position?: string;
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
companyLink?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SanitizedCertification {
|
||||||
|
body?: string;
|
||||||
|
name?: string;
|
||||||
|
year?: string;
|
||||||
|
link?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SanitizedEducation {
|
||||||
|
institution?: string;
|
||||||
|
degree?: string;
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SanitizedGoogleAnalytics {
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SanitizedHotjar {
|
||||||
|
id?: string;
|
||||||
|
snippetVersion: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SanitizedBlog {
|
||||||
|
display: boolean;
|
||||||
|
source: string;
|
||||||
|
username: string;
|
||||||
|
limit: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SanitizedCustomTheme {
|
||||||
|
primary: string;
|
||||||
|
secondary: string;
|
||||||
|
accent: string;
|
||||||
|
neutral: string;
|
||||||
|
'base-100': string;
|
||||||
|
'--rounded-box': string;
|
||||||
|
'--rounded-btn': string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SanitizedThemeConfig {
|
||||||
|
defaultTheme: string;
|
||||||
|
disableSwitch: boolean;
|
||||||
|
respectPrefersColorScheme: boolean;
|
||||||
|
displayAvatarRing: boolean;
|
||||||
|
themes: Array<string>;
|
||||||
|
customTheme: SanitizedCustomTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SanitizedConfig {
|
||||||
|
github: SanitizedGithub;
|
||||||
|
projects: SanitizedProjects;
|
||||||
|
seo: SanitizedSEO;
|
||||||
|
social: SanitizedSocial;
|
||||||
|
resume: SanitizedResume;
|
||||||
|
skills: Array<string>;
|
||||||
|
experiences: Array<SanitizedExperience>;
|
||||||
|
educations: Array<SanitizedEducation>;
|
||||||
|
certifications: Array<SanitizedCertification>;
|
||||||
|
googleAnalytics: SanitizedGoogleAnalytics;
|
||||||
|
hotjar: SanitizedHotjar;
|
||||||
|
blog: SanitizedBlog;
|
||||||
|
themeConfig: SanitizedThemeConfig;
|
||||||
|
footer?: string;
|
||||||
|
enablePWA: boolean;
|
||||||
|
}
|
||||||
9
src/main.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import GitProfile from './components/gitprofile.tsx';
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<GitProfile config={CONFIG} />
|
||||||
|
</React.StrictMode>,
|
||||||
|
);
|
||||||
@@ -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;
|
|
||||||
@@ -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';
|
|
||||||
218
src/utils/index.tsx
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
import { LOCAL_STORAGE_KEY_NAME } from '../constants';
|
||||||
|
import { DEFAULT_CUSTOM_THEME } from '../constants/default-custom-theme';
|
||||||
|
import { DEFAULT_THEMES } from '../constants/default-themes';
|
||||||
|
import {
|
||||||
|
SanitizedConfig,
|
||||||
|
SanitizedHotjar,
|
||||||
|
SanitizedThemeConfig,
|
||||||
|
} from '../interfaces/sanitized-config';
|
||||||
|
import { hotjar } from 'react-hotjar';
|
||||||
|
import colors from '../data/colors.json';
|
||||||
|
|
||||||
|
export const isDarkishTheme = (appliedTheme: string): boolean => {
|
||||||
|
return ['dark', 'halloween', 'forest', 'black', 'luxury', 'dracula'].includes(
|
||||||
|
appliedTheme,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type EventParams = {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Colors = {
|
||||||
|
[key: string]: { color: string | null; url: string };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSanitizedConfig = (
|
||||||
|
config: Config,
|
||||||
|
): SanitizedConfig | Record<string, never> => {
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
github: {
|
||||||
|
username: config.github.username,
|
||||||
|
},
|
||||||
|
projects: {
|
||||||
|
github: {
|
||||||
|
display: config?.projects?.github?.display ?? true,
|
||||||
|
header: config?.projects?.github?.header || 'Github Projects',
|
||||||
|
mode: config?.projects?.github?.mode || 'automatic',
|
||||||
|
automatic: {
|
||||||
|
sortBy: config?.projects?.github?.automatic?.sortBy || 'stars',
|
||||||
|
limit: config?.projects?.github?.automatic?.limit || 8,
|
||||||
|
exclude: {
|
||||||
|
forks:
|
||||||
|
config?.projects?.github?.automatic?.exclude?.forks || false,
|
||||||
|
projects:
|
||||||
|
config?.projects?.github?.automatic?.exclude?.projects || [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
manual: {
|
||||||
|
projects: config?.projects?.github?.manual?.projects || [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
external: {
|
||||||
|
header: config?.projects?.external?.header || 'My Projects',
|
||||||
|
projects: config?.projects?.external?.projects || [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
seo: {
|
||||||
|
title: config?.seo?.title,
|
||||||
|
description: config?.seo?.description,
|
||||||
|
imageURL: config?.seo?.imageURL,
|
||||||
|
},
|
||||||
|
social: {
|
||||||
|
linkedin: config?.social?.linkedin,
|
||||||
|
twitter: config?.social?.twitter,
|
||||||
|
mastodon: config?.social?.mastodon,
|
||||||
|
facebook: config?.social?.facebook,
|
||||||
|
instagram: config?.social?.instagram,
|
||||||
|
youtube: config?.social?.youtube,
|
||||||
|
dribbble: config?.social?.dribbble,
|
||||||
|
behance: config?.social?.behance,
|
||||||
|
medium: config?.social?.medium,
|
||||||
|
dev: config?.social?.dev,
|
||||||
|
stackoverflow: config?.social?.stackoverflow,
|
||||||
|
website: config?.social?.website,
|
||||||
|
phone: config?.social?.phone,
|
||||||
|
email: config?.social?.email,
|
||||||
|
skype: config?.social?.skype,
|
||||||
|
telegram: config?.social?.telegram,
|
||||||
|
},
|
||||||
|
resume: {
|
||||||
|
fileUrl: config?.resume?.fileUrl || '',
|
||||||
|
},
|
||||||
|
skills: config?.skills || [],
|
||||||
|
experiences: config?.experiences || [],
|
||||||
|
certifications: config?.certifications || [],
|
||||||
|
educations: config?.educations || [],
|
||||||
|
googleAnalytics: {
|
||||||
|
id: config?.googleAnalytics?.id,
|
||||||
|
},
|
||||||
|
hotjar: {
|
||||||
|
id: config?.hotjar?.id,
|
||||||
|
snippetVersion: config?.hotjar?.snippetVersion || 6,
|
||||||
|
},
|
||||||
|
blog: {
|
||||||
|
username: config?.blog?.username || '',
|
||||||
|
source: config?.blog?.source || 'dev',
|
||||||
|
limit: config?.blog?.limit || 5,
|
||||||
|
display: !!config?.blog?.username && !!config?.blog?.source,
|
||||||
|
},
|
||||||
|
themeConfig: {
|
||||||
|
defaultTheme: config?.themeConfig?.defaultTheme || DEFAULT_THEMES[0],
|
||||||
|
disableSwitch: config?.themeConfig?.disableSwitch || false,
|
||||||
|
respectPrefersColorScheme:
|
||||||
|
config?.themeConfig?.respectPrefersColorScheme || false,
|
||||||
|
displayAvatarRing: config?.themeConfig?.displayAvatarRing ?? true,
|
||||||
|
themes: config?.themeConfig?.themes || DEFAULT_THEMES,
|
||||||
|
customTheme: {
|
||||||
|
primary:
|
||||||
|
config?.themeConfig?.customTheme?.primary ||
|
||||||
|
DEFAULT_CUSTOM_THEME.primary,
|
||||||
|
secondary:
|
||||||
|
config?.themeConfig?.customTheme?.secondary ||
|
||||||
|
DEFAULT_CUSTOM_THEME.secondary,
|
||||||
|
accent:
|
||||||
|
config?.themeConfig?.customTheme?.accent ||
|
||||||
|
DEFAULT_CUSTOM_THEME.accent,
|
||||||
|
neutral:
|
||||||
|
config?.themeConfig?.customTheme?.neutral ||
|
||||||
|
DEFAULT_CUSTOM_THEME.neutral,
|
||||||
|
'base-100':
|
||||||
|
config?.themeConfig?.customTheme?.['base-100'] ||
|
||||||
|
DEFAULT_CUSTOM_THEME['base-100'],
|
||||||
|
'--rounded-box':
|
||||||
|
config?.themeConfig?.customTheme?.['--rounded-box'] ||
|
||||||
|
DEFAULT_CUSTOM_THEME['--rounded-box'],
|
||||||
|
'--rounded-btn':
|
||||||
|
config?.themeConfig?.customTheme?.['--rounded-btn'] ||
|
||||||
|
DEFAULT_CUSTOM_THEME['--rounded-btn'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
footer: config?.footer,
|
||||||
|
enablePWA: config?.enablePWA ?? true,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getInitialTheme = (themeConfig: SanitizedThemeConfig): string => {
|
||||||
|
if (themeConfig.disableSwitch) {
|
||||||
|
return themeConfig.defaultTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof window !== 'undefined' &&
|
||||||
|
!(localStorage.getItem(LOCAL_STORAGE_KEY_NAME) === null)
|
||||||
|
) {
|
||||||
|
const savedTheme = localStorage.getItem(LOCAL_STORAGE_KEY_NAME);
|
||||||
|
|
||||||
|
if (savedTheme && themeConfig.themes.includes(savedTheme)) {
|
||||||
|
return savedTheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = ({
|
||||||
|
widthCls = null,
|
||||||
|
heightCls = null,
|
||||||
|
style = {} as React.CSSProperties,
|
||||||
|
shape = 'rounded-full',
|
||||||
|
className = null,
|
||||||
|
}: {
|
||||||
|
widthCls?: string | null;
|
||||||
|
heightCls?: string | null;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
shape?: string;
|
||||||
|
className?: string | null;
|
||||||
|
}): JSX.Element => {
|
||||||
|
const classNames = ['bg-base-300', 'animate-pulse', shape];
|
||||||
|
if (className) {
|
||||||
|
classNames.push(className);
|
||||||
|
}
|
||||||
|
if (widthCls) {
|
||||||
|
classNames.push(widthCls);
|
||||||
|
}
|
||||||
|
if (heightCls) {
|
||||||
|
classNames.push(heightCls);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className={classNames.join(' ')} style={style} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setupHotjar = (hotjarConfig: SanitizedHotjar): void => {
|
||||||
|
if (hotjarConfig?.id) {
|
||||||
|
const snippetVersion = hotjarConfig?.snippetVersion || 6;
|
||||||
|
hotjar.initialize(parseInt(hotjarConfig.id), snippetVersion);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ga = {
|
||||||
|
event(action: string, params: EventParams): void {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(window as any)?.gtag('event', action, params);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getLanguageColor = (language: string): string => {
|
||||||
|
const languageColors: Colors = colors;
|
||||||
|
if (typeof languageColors[language] !== 'undefined') {
|
||||||
|
return languageColors[language].color || 'gray';
|
||||||
|
} else {
|
||||||
|
return 'gray';
|
||||||
|
}
|
||||||
|
};
|
||||||
1
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
module.exports = {
|
import CONFIG from './gitprofile.config';
|
||||||
purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
|
|
||||||
darkMode: false, // or 'media' or 'class'
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
variants: {
|
plugins: [require('daisyui')],
|
||||||
extend: {},
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
require('daisyui')
|
|
||||||
],
|
|
||||||
daisyui: {
|
daisyui: {
|
||||||
logs: false
|
logs: false,
|
||||||
|
themes: [
|
||||||
|
...CONFIG.themeConfig.themes,
|
||||||
|
{ procyon: CONFIG.themeConfig.customTheme },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
25
tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
},
|
||||||
|
"include": ["src", "global.d.ts"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }],
|
||||||
|
}
|
||||||
10
tsconfig.node.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["gitprofile.config.ts", "vite.config.ts"]
|
||||||
|
}
|
||||||
48
vite.config.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
import { VitePWA } from 'vite-plugin-pwa';
|
||||||
|
import CONFIG from './gitprofile.config';
|
||||||
|
import { createHtmlPlugin } from 'vite-plugin-html';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
base: CONFIG.base || '/',
|
||||||
|
plugins: [
|
||||||
|
react(),
|
||||||
|
createHtmlPlugin({
|
||||||
|
inject: {
|
||||||
|
data: {
|
||||||
|
metaTitle: CONFIG.seo.title,
|
||||||
|
metaDescription: CONFIG.seo.description,
|
||||||
|
metaImageURL: CONFIG.seo.imageURL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...(CONFIG.enablePWA
|
||||||
|
? [
|
||||||
|
VitePWA({
|
||||||
|
registerType: 'autoUpdate',
|
||||||
|
workbox: {
|
||||||
|
navigateFallback: undefined,
|
||||||
|
},
|
||||||
|
includeAssets: ['logo.png'],
|
||||||
|
manifest: {
|
||||||
|
name: 'Portfolio',
|
||||||
|
short_name: 'Portfolio',
|
||||||
|
description: 'Personal Portfolio',
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: 'logo.png',
|
||||||
|
sizes: '64x64 32x32 24x24 16x16 192x192 512x512',
|
||||||
|
type: 'image/png',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
],
|
||||||
|
define: {
|
||||||
|
CONFIG: CONFIG,
|
||||||
|
},
|
||||||
|
});
|
||||||