Compare commits
276 Commits
1.0
...
frontend/c
Author | SHA1 | Date | |
---|---|---|---|
|
c97670594e | ||
|
1962c2047d | ||
|
84cdade1a3 | ||
|
8a296d99c4 | ||
|
10f9043516 | ||
|
37155e7d3c | ||
|
ff7d24d725 | ||
|
7794386573 | ||
|
952884ec99 | ||
|
22fa464cca | ||
|
66f939f361 | ||
|
25b6700812 | ||
|
7c53be8892 | ||
|
be61c144e0 | ||
|
537ffc365e | ||
|
dd7306cd6d | ||
|
ed1c8e3709 | ||
|
36e2e95ab9 | ||
|
7309e9640c | ||
|
be17f594fe | ||
|
06329b27c1 | ||
|
0e05128980 | ||
|
9a22a457f5 | ||
|
985da9ae30 | ||
|
47e2a9b968 | ||
|
074adee8f0 | ||
|
9f68c096a9 | ||
|
e4654022f0 | ||
|
c4291394fe | ||
|
9b29d240e3 | ||
|
d09c91247e | ||
|
51895be81e | ||
|
a79b65a1b4 | ||
|
3fe41d2071 | ||
|
b25111415f | ||
|
0c3ea386f5 | ||
|
d1f6951da2 | ||
|
7ac1dd9b9c | ||
|
ba21172d20 | ||
|
93b538f4e1 | ||
|
7cac02f4b4 | ||
|
42e98e9cc6 | ||
|
0221964362 | ||
|
cbf0b2c496 | ||
|
1ff9c11361 | ||
|
b681aaa52e | ||
|
8a99ecbecd | ||
|
82456021ad | ||
|
6eba4a2afd | ||
|
c3704107a3 | ||
|
102cd8885c | ||
|
8259a83921 | ||
|
c1ecf9d8a5 | ||
|
381801120e | ||
|
fe458e0790 | ||
|
a42e92781a | ||
|
8255f69257 | ||
|
30e6c15ddb | ||
|
3eb13043ce | ||
|
16b2019d06 | ||
|
a0a5e74281 | ||
|
5881c27526 | ||
|
c16bed02ca | ||
|
37432582c0 | ||
|
0d71b2b1e6 | ||
|
ff3d0489cc | ||
|
a94af0ec14 | ||
|
4f1d4feff6 | ||
|
7bc3b4a0f3 | ||
|
9d9a6506cc | ||
|
3b47cd3542 | ||
|
f500d49275 | ||
|
098303b78b | ||
|
bf56db60bc | ||
|
cdc362625a | ||
|
2ec335edc8 | ||
|
064f97108e | ||
|
7d0bc2e8a2 | ||
|
ea4de11e30 | ||
|
4e7d8512cc | ||
|
91ef32722a | ||
|
87fba80759 | ||
|
0acd1d1fcd | ||
|
5228fe936f | ||
|
b221016269 | ||
|
f7d1d61528 | ||
|
1467865e45 | ||
|
17e2ffd110 | ||
|
d177a13aa6 | ||
|
1afe684699 | ||
|
1fbdf68573 | ||
|
3feea57204 | ||
|
00d8877a12 | ||
|
67c4407d30 | ||
|
69679addc3 | ||
|
272eafa013 | ||
|
a19b4da20d | ||
|
47349f00c2 | ||
|
dbedae78ca | ||
|
5733476fd9 | ||
|
5c17deddf5 | ||
|
f1b5357358 | ||
|
6a319fe6cc | ||
|
269e9eb90b | ||
|
844a38e739 | ||
|
6c8b899a35 | ||
|
f246b065a1 | ||
|
97826210fa | ||
|
ff5706c82f | ||
|
609cce403a | ||
|
87ad491b2c | ||
|
ef25a14af1 | ||
|
3ac1de7fb0 | ||
|
09e8796159 | ||
|
0d3c3eea11 | ||
|
992b6990a3 | ||
|
ffa18538ff | ||
|
65307e7cc1 | ||
|
46d9fd66f3 | ||
|
2344e6c4c3 | ||
|
8899b0d196 | ||
|
d4613330f4 | ||
|
283632713a | ||
|
d3b96d20cc | ||
|
374e8a04fd | ||
|
f1b6d13928 | ||
|
6a4af77ba8 | ||
|
a2db93873b | ||
|
f523b077a4 | ||
|
412eaa004e | ||
|
8e71b3c448 | ||
|
12030deeea | ||
|
5a89ecf2d8 | ||
|
991d344a77 | ||
|
cc06819a50 | ||
|
eb1beecfa7 | ||
|
135ef6a11a | ||
|
e902c062fc | ||
|
685c402f28 | ||
|
c88e04f809 | ||
|
d2167555da | ||
|
3f61854ccc | ||
|
9ca201709d | ||
|
0c3486df37 | ||
|
27d3704390 | ||
|
86bd47c45d | ||
|
b8c2056557 | ||
|
c190a94bc6 | ||
|
eafbcd5867 | ||
|
170b14df4c | ||
|
694315026e | ||
|
a9183dacc7 | ||
|
d46b543659 | ||
|
9a63e576c7 | ||
|
69ef7615fe | ||
|
68950ab2c9 | ||
|
305d0a41ac | ||
|
e43c21a01d | ||
|
b6205af02c | ||
|
6de2867d8a | ||
|
13612323d7 | ||
|
edd10470a7 | ||
|
d5b8d761d5 | ||
|
9f7119694b | ||
|
8fbde17c22 | ||
|
e16947bd78 | ||
|
33af054161 | ||
|
8913d5c5e4 | ||
|
0040d568b1 | ||
|
706070e42a | ||
|
f79fb62ff9 | ||
|
5c428540dc | ||
|
35ae0a74b3 | ||
|
a1606521d4 | ||
|
6545c8de36 | ||
|
0f713cbfd8 | ||
|
333894b75b | ||
|
c73a500ffd | ||
|
a489bc0fde | ||
|
09ef6bfbb0 | ||
|
67d2f5cb57 | ||
|
b28deab192 | ||
|
290a73c8b0 | ||
|
579d39e104 | ||
|
e7c32958c9 | ||
|
4a31a757f1 | ||
|
c7a141c024 | ||
|
b4042cb183 | ||
|
0d937384a4 | ||
|
3ee4d49d05 | ||
|
bc9e58c155 | ||
|
0a6d72f671 | ||
|
dd43969dfb | ||
|
0067cfdbc6 | ||
|
6bb1505db3 | ||
|
7e0a392a87 | ||
|
1c94fb8b95 | ||
|
f03472127c | ||
|
d5b091a5ed | ||
|
ba1b876e14 | ||
|
af8a79c48d | ||
|
6ba22799cd | ||
|
63b297ca34 | ||
|
929e6c0a51 | ||
|
9b97fce431 | ||
|
ad427eceee | ||
|
1b9bb03cde | ||
|
68fc45104d | ||
|
96e02195a9 | ||
|
e800e3b6ce | ||
|
ac984346e8 | ||
|
afa0e032e7 | ||
|
86f599b5b7 | ||
|
75c4f952dd | ||
|
aa8aea9433 | ||
|
93797cc5ee | ||
|
fc159ecb9b | ||
|
c097017520 | ||
|
ec1725e312 | ||
|
67b8af3f6b | ||
|
74ef38cdb6 | ||
|
0e90d53b8e | ||
|
ec0cd4692c | ||
|
964a87f605 | ||
|
7c3c552ff1 | ||
|
810cea40b3 | ||
|
ae463059dd | ||
|
3f2ef9ae48 | ||
|
8d05031e51 | ||
|
2c0d1ef77f | ||
|
3a7099bebe | ||
|
8bb36c1451 | ||
|
d3ee830e26 | ||
|
8013c64eb5 | ||
|
3bdac7c7d9 | ||
|
101aed8257 | ||
|
d1bdcfd1da | ||
|
0a3fd7b894 | ||
|
1c065f563d | ||
|
85ac2162bc | ||
|
fe34e7f8d7 | ||
|
13817a4318 | ||
|
197f1f6824 | ||
|
c37297f822 | ||
|
a85497d7e7 | ||
|
4737dd8390 | ||
|
309731fe1b | ||
|
8902100baa | ||
|
bdee14009b | ||
|
55f7d00e83 | ||
|
4459b7d062 | ||
|
c188fc57ae | ||
|
ef6709d2bf | ||
|
51a5f570fe | ||
|
74791e1f7d | ||
|
7d27cb0b9f | ||
|
6dbf170076 | ||
|
461c9a03a7 | ||
|
41ec8327c4 | ||
|
c8395aa3df | ||
|
d80327c8e8 | ||
|
b4ce36fb04 | ||
|
c84e7d41bb | ||
|
14c03912f9 | ||
|
0028d4f80c | ||
|
46f1034163 | ||
|
c44fc62e8a | ||
|
7a6a8c94f9 | ||
|
cadf9ba4a7 | ||
|
4428e8d82b | ||
|
b080ed64ba | ||
|
3bfd06d704 | ||
|
0d21be3796 | ||
|
f15e7a18b8 | ||
|
9c81d818ed | ||
|
5cf17a3e0f |
13
.babelrc
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
"next/babel"
|
||||
],
|
||||
"plugins": [
|
||||
[
|
||||
"styled-components",
|
||||
{
|
||||
"ssr": true
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
# EditorConfig for the node-soap library - head over to editorconfig.org to see if you editor supports this file.
|
||||
|
||||
# this is the topmost .editorconfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
[*.xml]
|
||||
insert_final_newline = false
|
||||
|
||||
[*.js]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[{package.json, .travis.yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
18
.eslintrc
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": [
|
||||
"next",
|
||||
"next/core-web-vitals",
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
"@next/next/no-img-element": [
|
||||
"off"
|
||||
],
|
||||
"react/display-name": [
|
||||
"off"
|
||||
],
|
||||
"react/jsx-no-target-blank": [
|
||||
"off"
|
||||
]
|
||||
}
|
||||
}
|
1
.github/FUNDING.yml
vendored
@@ -1,3 +1,2 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: kamranahmedse
|
||||
|
BIN
.github/images/banner.png
vendored
Normal file
After Width: | Height: | Size: 39 KiB |
17
.github/sponsors/doppler-logo.svg
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
<svg width="527" height="83" viewBox="0 0 527 83" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M135.783 59.3472C136.392 59.4025 137.083 59.4578 137.858 59.5132C138.688 59.5132 139.656 59.5132 140.763 59.5132C147.237 59.5132 152.023 57.8808 155.122 54.6162C158.276 51.3515 159.853 46.8418 159.853 41.0872C159.853 35.0558 158.359 30.4908 155.371 27.3921C152.383 24.2935 147.652 22.7441 141.178 22.7441C140.293 22.7441 139.38 22.7718 138.439 22.8271C138.068 22.8271 137.705 22.8358 137.351 22.853C136.45 22.8969 135.783 23.6663 135.783 24.5677V59.3472ZM173.216 41.0872C173.216 46.0672 172.441 50.4108 170.892 54.1182C169.343 57.8255 167.129 60.8965 164.252 63.3312C161.43 65.7658 157.972 67.5918 153.877 68.8092C149.782 70.0265 145.19 70.6352 140.099 70.6352C137.775 70.6352 135.064 70.5245 131.965 70.3032C129.364 70.1638 126.801 69.8684 124.278 69.4171C123.436 69.2666 122.835 68.527 122.835 67.6721V13.1161C125.823 12.5628 128.922 12.2031 132.131 12.0371C135.396 11.8158 138.19 11.7051 140.514 11.7051C145.439 11.7051 149.893 12.2585 153.877 13.3651C157.916 14.4718 161.375 16.2148 164.252 18.5941C167.129 20.9735 169.343 24.0168 170.892 27.7241C172.441 31.4315 173.216 35.8858 173.216 41.0872Z" fill="#111111"/>
|
||||
<path d="M199.262 41.0872C199.262 43.9092 199.594 46.4545 200.258 48.7232C200.977 50.9918 201.973 52.9562 203.246 54.6162C204.574 56.2208 206.178 57.4658 208.06 58.3512C209.941 59.2365 212.099 59.6792 214.534 59.6792C216.913 59.6792 219.043 59.2365 220.925 58.3512C222.861 57.4658 224.466 56.2208 225.739 54.6162C227.067 52.9562 228.063 50.9918 228.727 48.7232C229.446 46.4545 229.806 43.9092 229.806 41.0872C229.806 38.2652 229.446 35.7198 228.727 33.4511C228.063 31.1271 227.067 29.1628 225.739 27.5581C224.466 25.8981 222.861 24.6255 220.925 23.7401C219.043 22.8548 216.913 22.4121 214.534 22.4121C212.099 22.4121 209.941 22.8825 208.06 23.8231C206.178 24.7085 204.574 25.9811 203.246 27.6411C201.973 29.2458 200.977 31.2101 200.258 33.5341C199.594 35.8028 199.262 38.3205 199.262 41.0872ZM243.169 41.0872C243.169 46.0118 242.422 50.3555 240.928 54.1182C239.489 57.8255 237.497 60.9518 234.952 63.4972C232.406 65.9872 229.363 67.8685 225.822 69.1412C222.336 70.4138 218.573 71.0502 214.534 71.0502C210.605 71.0502 206.898 70.4138 203.412 69.1412C199.926 67.8685 196.882 65.9872 194.282 63.4972C191.681 60.9518 189.634 57.8255 188.14 54.1182C186.646 50.3555 185.899 46.0118 185.899 41.0872C185.899 36.1625 186.673 31.8465 188.223 28.1391C189.772 24.3765 191.847 21.2225 194.448 18.6771C197.104 16.1318 200.147 14.2228 203.578 12.9501C207.064 11.6775 210.716 11.0411 214.534 11.0411C218.462 11.0411 222.17 11.6775 225.656 12.9501C229.142 14.2228 232.185 16.1318 234.786 18.6771C237.386 21.2225 239.434 24.3765 240.928 28.1391C242.422 31.8465 243.169 36.1625 243.169 41.0872Z" fill="#111111"/>
|
||||
<path d="M275.584 11.7051C284.16 11.7051 290.745 13.2268 295.338 16.2701C299.93 19.2581 302.227 24.1828 302.227 31.0441C302.227 37.9608 299.903 42.9685 295.255 46.0672C290.607 49.1105 283.967 50.6322 275.335 50.6322H273.066C272.073 50.6322 271.268 51.4373 271.268 52.4306V69.8882H260.118C259.125 69.8882 258.32 69.083 258.32 68.0897V13.1991C261.141 12.6458 264.13 12.2585 267.284 12.0371C270.438 11.8158 273.204 11.7051 275.584 11.7051ZM276.414 22.7441C275.473 22.7441 274.532 22.7718 273.592 22.8271C273.359 22.8417 273.135 22.8562 272.918 22.8707C271.984 22.9331 271.268 23.7126 271.268 24.6479V39.5931H275.335C279.817 39.5931 283.192 38.9845 285.461 37.7671C287.729 36.5498 288.864 34.2811 288.864 30.9611C288.864 29.3565 288.559 28.0285 287.951 26.9771C287.397 25.9258 286.567 25.0958 285.461 24.4871C284.409 23.8231 283.109 23.3805 281.56 23.1591C280.01 22.8825 278.295 22.7441 276.414 22.7441Z" fill="#111111"/>
|
||||
<path d="M333.356 11.7051C341.933 11.7051 348.517 13.2268 353.11 16.2701C357.703 19.2581 359.999 24.1828 359.999 31.0441C359.999 37.9608 357.675 42.9685 353.027 46.0672C348.379 49.1105 341.739 50.6322 333.107 50.6322H330.838C329.845 50.6322 329.04 51.4373 329.04 52.4306V69.8882H317.89C316.897 69.8882 316.092 69.083 316.092 68.0897V13.1991C318.914 12.6458 321.902 12.2585 325.056 12.0371C328.21 11.8158 330.977 11.7051 333.356 11.7051ZM334.186 22.7441C333.245 22.7441 332.305 22.7718 331.364 22.8271C331.132 22.8417 330.907 22.8562 330.69 22.8707C329.757 22.9331 329.04 23.7126 329.04 24.6479V39.5931H333.107C337.589 39.5931 340.964 38.9845 343.233 37.7671C345.502 36.5498 346.636 34.2811 346.636 30.9611C346.636 29.3565 346.332 28.0285 345.723 26.9771C345.17 25.9258 344.34 25.0958 343.233 24.4871C342.182 23.8231 340.881 23.3805 339.332 23.1591C337.783 22.8825 336.067 22.7441 334.186 22.7441Z" fill="#111111"/>
|
||||
<path d="M410.14 58.7662C410.472 58.7662 410.74 59.0345 410.74 59.3656V69.2887C410.74 69.6198 410.472 69.8882 410.14 69.8882H375.663C374.67 69.8882 373.865 69.083 373.865 68.0897V17.3101C373.865 16.5002 374.405 15.7901 375.186 15.5748L386.813 12.3691V58.7662H410.14Z" fill="#111111"/>
|
||||
<path d="M426.789 69.8882C425.796 69.8882 424.991 69.083 424.991 68.0897V12.3691H464.944C465.344 12.3691 465.632 12.753 465.52 13.1367L462.694 22.8107C462.62 23.0664 462.385 23.2421 462.119 23.2421H439.737C438.744 23.2421 437.939 24.0473 437.939 25.0406V34.5301H458.554C458.953 34.5301 459.241 34.9136 459.129 35.2972L456.384 44.7223C456.31 44.9782 456.075 45.1542 455.809 45.1542H439.737C438.744 45.1542 437.939 45.9593 437.939 46.9526V59.0152H466.05C466.449 59.0152 466.737 59.3983 466.626 59.7818L463.817 69.4558C463.743 69.7119 463.508 69.8882 463.242 69.8882H426.789Z" fill="#111111"/>
|
||||
<path d="M496.532 11.7051C505.164 11.7051 511.776 13.2545 516.369 16.3531C520.962 19.3965 523.258 24.1551 523.258 30.6291C523.258 34.6685 522.317 37.9608 520.436 40.5061C518.61 42.9961 515.954 44.9605 512.468 46.3992C513.63 47.8378 514.847 49.4978 516.12 51.3792C517.393 53.2052 518.638 55.1418 519.855 57.1892C521.128 59.1812 522.345 61.2838 523.507 63.4972C524.512 65.3633 525.455 67.2088 526.335 69.0336C526.527 69.4299 526.237 69.8882 525.797 69.8882H512.571C512.354 69.8882 512.154 69.7707 512.047 69.5812C511.05 67.7993 510.029 65.9926 508.982 64.1612C507.931 62.2245 506.824 60.3432 505.662 58.5172C504.555 56.6912 503.449 54.9758 502.342 53.3712C501.235 51.7112 500.129 50.2172 499.022 48.8892H494.43C493.436 48.8892 492.631 49.6943 492.631 50.6876V69.8882H481.482C480.488 69.8882 479.683 69.083 479.683 68.0897V13.1991C482.505 12.6458 485.41 12.2585 488.398 12.0371C491.441 11.8158 494.153 11.7051 496.532 11.7051ZM497.279 22.7441C496.338 22.7441 495.481 22.7718 494.706 22.8271C494.563 22.8381 494.421 22.8491 494.281 22.8601C493.348 22.933 492.631 23.7126 492.631 24.6479V38.5972H496.283C501.152 38.5972 504.638 37.9885 506.741 36.7711C508.844 35.5538 509.895 33.4788 509.895 30.5461C509.895 27.7241 508.816 25.7321 506.658 24.5701C504.555 23.3528 501.429 22.7441 497.279 22.7441Z" fill="#111111"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.52378 0C0.860841 0 0.468192 0.732463 0.825109 1.29112L15.7055 24.5822C16.936 26.5083 19.0574 27.6739 21.343 27.6739H41.4698C48.8467 27.6739 53.4585 34.1966 53.4315 41.5C53.4045 48.8034 48.49 55.2611 41.4698 55.2611H19.982C18.7491 55.2611 17.7542 56.2606 17.7542 57.4936V80.7699C17.7542 82.0016 18.747 83 19.9786 83H47.4274C71.1783 83 82.9858 60.4663 83.0002 41.5C83.0146 22.5337 71.3453 0 47.4274 0H1.52378ZM11.2867 55.3259H5.94311C2.66082 55.3259 0 58.4222 0 62.2417V73.2033V82.3895C0 82.7206 0.268396 82.989 0.599478 82.989H5.94311C9.22539 82.989 11.8862 79.8927 11.8862 76.0732V55.9254C11.8862 55.5943 11.6178 55.3259 11.2867 55.3259Z" fill="url(#paint0_linear)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="66.2481" y1="-2.76031" x2="7.59388" y2="75.9105" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.0364583" stop-color="#409FF8"/>
|
||||
<stop offset="0.5" stop-color="#1673FF"/>
|
||||
<stop offset="1" stop-color="#516FF7"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 7.7 KiB |
59
.github/sponsors/oss-logo.svg
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 646.6 105.7" style="enable-background:new 0 0 646.6 105.7;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#104366;}
|
||||
.st1{fill:#4086C6;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M21.1,79.8c-6.6-3.5-11.7-8.4-15.5-14.6C1.9,59,0,52,0,44.3c0-7.8,1.9-14.7,5.6-20.9
|
||||
c3.7-6.2,8.9-11.1,15.5-14.6c6.6-3.5,14-5.3,22.2-5.3c8.2,0,15.6,1.8,22.1,5.3c6.6,3.5,11.7,8.4,15.5,14.6
|
||||
c3.8,6.2,5.6,13.2,5.6,20.9c0,7.8-1.9,14.7-5.6,20.9c-3.8,6.2-8.9,11.1-15.5,14.6c-6.5,3.5-13.9,5.3-22.1,5.3
|
||||
C35,85.1,27.6,83.4,21.1,79.8z M55.9,66.3c3.8-2.1,6.7-5.1,8.9-9c2.1-3.8,3.2-8.2,3.2-13.1c0-4.9-1.1-9.3-3.2-13.1
|
||||
c-2.1-3.8-5.1-6.8-8.9-9c-3.8-2.1-8-3.2-12.6-3.2c-4.7,0-8.9,1.1-12.6,3.2s-6.7,5.1-8.9,9c-2.1,3.8-3.2,8.2-3.2,13.1
|
||||
c0,4.9,1.1,9.3,3.2,13.1c2.1,3.8,5.1,6.8,8.9,9c3.8,2.1,8,3.2,12.6,3.2C47.9,69.5,52.1,68.5,55.9,66.3z"/>
|
||||
<path class="st0" d="M108.1,82.6c-5.8-1.7-10.5-3.9-14.1-6.6l6.2-13.8c3.4,2.5,7.4,4.5,12.1,6c4.7,1.5,9.3,2.3,14,2.3
|
||||
c5.2,0,9-0.8,11.5-2.3c2.5-1.5,3.7-3.6,3.7-6.2c0-1.9-0.7-3.4-2.2-4.7c-1.5-1.2-3.3-2.2-5.6-3c-2.3-0.8-5.4-1.6-9.3-2.5
|
||||
c-6-1.4-11-2.9-14.8-4.3c-3.8-1.4-7.1-3.7-9.9-6.9c-2.7-3.2-4.1-7.4-4.1-12.6c0-4.6,1.2-8.7,3.7-12.5c2.5-3.7,6.2-6.7,11.2-8.9
|
||||
c5-2.2,11.1-3.3,18.3-3.3c5,0,10,0.6,14.8,1.8c4.8,1.2,9,2.9,12.6,5.2l-5.6,13.9c-7.3-4.1-14.6-6.2-21.9-6.2
|
||||
c-5.1,0-8.9,0.8-11.3,2.5c-2.4,1.7-3.7,3.8-3.7,6.5c0,2.7,1.4,4.7,4.2,6c2.8,1.3,7.1,2.6,12.9,3.9c6,1.4,11,2.9,14.8,4.3
|
||||
c3.8,1.4,7.1,3.7,9.9,6.8c2.7,3.1,4.1,7.3,4.1,12.5c0,4.5-1.3,8.6-3.8,12.4c-2.5,3.7-6.3,6.7-11.3,8.9c-5,2.2-11.2,3.3-18.4,3.3
|
||||
C120,85.1,113.9,84.3,108.1,82.6z"/>
|
||||
<path class="st0" d="M180.1,82.6c-5.8-1.7-10.5-3.9-14.1-6.6l6.2-13.8c3.4,2.5,7.4,4.5,12.1,6c4.7,1.5,9.3,2.3,14,2.3
|
||||
c5.2,0,9-0.8,11.5-2.3c2.5-1.5,3.7-3.6,3.7-6.2c0-1.9-0.7-3.4-2.2-4.7c-1.5-1.2-3.3-2.2-5.6-3c-2.3-0.8-5.4-1.6-9.3-2.5
|
||||
c-6-1.4-10.9-2.9-14.8-4.3c-3.8-1.4-7.1-3.7-9.9-6.9c-2.7-3.2-4.1-7.4-4.1-12.6c0-4.6,1.2-8.7,3.7-12.5c2.5-3.7,6.2-6.7,11.2-8.9
|
||||
s11.1-3.3,18.3-3.3c5,0,10,0.6,14.8,1.8c4.8,1.2,9,2.9,12.6,5.2l-5.6,13.9c-7.3-4.1-14.6-6.2-21.9-6.2c-5.1,0-8.9,0.8-11.3,2.5
|
||||
c-2.4,1.7-3.7,3.8-3.7,6.5c0,2.7,1.4,4.7,4.2,6c2.8,1.3,7.1,2.6,12.9,3.9c6,1.4,10.9,2.9,14.8,4.3s7.1,3.7,9.9,6.8
|
||||
c2.7,3.1,4.1,7.3,4.1,12.5c0,4.5-1.3,8.6-3.8,12.4c-2.5,3.7-6.3,6.7-11.3,8.9c-5,2.2-11.2,3.3-18.4,3.3
|
||||
C192,85.1,186,84.3,180.1,82.6z"/>
|
||||
<path class="st1" d="M293.2,79.1c-6.2-3.5-11.1-8.2-14.7-14.3c-3.6-6.1-5.4-12.9-5.4-20.5c0-7.6,1.8-14.5,5.4-20.5
|
||||
c3.6-6.1,8.5-10.9,14.7-14.3c6.2-3.5,13.2-5.2,20.9-5.2c5.7,0,11,0.9,15.8,2.8c4.8,1.8,8.9,4.6,12.3,8.2l-3.6,3.7
|
||||
c-6.3-6.2-14.4-9.4-24.3-9.4c-6.6,0-12.6,1.5-18.1,4.5c-5.4,3-9.7,7.2-12.8,12.5s-4.6,11.2-4.6,17.8s1.5,12.5,4.6,17.8
|
||||
c3.1,5.3,7.3,9.5,12.8,12.5c5.4,3,11.4,4.5,18.1,4.5c9.8,0,17.9-3.2,24.3-9.5l3.6,3.7c-3.4,3.6-7.5,6.4-12.4,8.2
|
||||
c-4.9,1.9-10.1,2.8-15.7,2.8C306.3,84.3,299.4,82.6,293.2,79.1z"/>
|
||||
<path class="st1" d="M395.4,30c3.9,3.7,5.9,9.2,5.9,16.4v37.4h-5.4V73.3c-1.9,3.5-4.6,6.2-8.2,8.1c-3.6,1.9-7.9,2.9-13,2.9
|
||||
c-6.5,0-11.7-1.5-15.5-4.6c-3.8-3.1-5.7-7.1-5.7-12.2c0-4.9,1.8-8.9,5.2-11.9c3.5-3,9.1-4.6,16.8-4.6h20.2v-4.7
|
||||
c0-5.5-1.5-9.7-4.5-12.5c-3-2.9-7.3-4.3-13-4.3c-3.9,0-7.7,0.7-11.2,2c-3.6,1.4-6.6,3.2-9.1,5.4l-2.8-4.1c2.9-2.6,6.5-4.7,10.6-6.2
|
||||
c4.1-1.5,8.5-2.2,13-2.2C385.9,24.4,391.5,26.3,395.4,30z M387.9,76.2c3.4-2.3,6-5.5,7.7-9.8V55.3h-20.1c-5.8,0-10,1.1-12.6,3.2
|
||||
c-2.6,2.1-3.9,5-3.9,8.7c0,3.8,1.4,6.9,4.3,9.1c2.9,2.2,6.9,3.3,12.1,3.3C380.3,79.6,384.5,78.5,387.9,76.2z"/>
|
||||
<path class="st1" d="M469,28.2c4.4,2.6,7.9,6.1,10.4,10.6c2.5,4.5,3.8,9.7,3.8,15.5c0,5.8-1.3,11-3.8,15.5
|
||||
c-2.5,4.6-6,8.1-10.4,10.6c-4.4,2.5-9.4,3.8-14.9,3.8c-5.2,0-9.9-1.2-14.1-3.7c-4.2-2.4-7.5-5.9-9.8-10.2v35.3h-5.6V24.8h5.4v13.9
|
||||
c2.3-4.5,5.6-8,9.9-10.6c4.2-2.5,9-3.8,14.3-3.8C459.6,24.4,464.6,25.7,469,28.2z M465.9,76c3.6-2.1,6.5-5,8.5-8.8
|
||||
c2.1-3.8,3.1-8.1,3.1-12.9c0-4.8-1-9.1-3.1-12.9c-2.1-3.8-4.9-6.7-8.5-8.8c-3.6-2.1-7.7-3.2-12.2-3.2c-4.5,0-8.6,1.1-12.1,3.2
|
||||
c-3.6,2.1-6.4,5-8.5,8.8c-2.1,3.8-3.1,8.1-3.1,12.9c0,4.8,1,9.1,3.1,12.9c2.1,3.8,4.9,6.7,8.5,8.8c3.6,2.1,7.6,3.2,12.1,3.2
|
||||
C458.3,79.1,462.3,78.1,465.9,76z"/>
|
||||
<path class="st1" d="M500.3,9.2c-0.9-0.9-1.4-1.9-1.4-3.2c0-1.3,0.5-2.4,1.4-3.3c0.9-0.9,2-1.4,3.3-1.4c1.3,0,2.4,0.4,3.3,1.3
|
||||
c0.9,0.9,1.4,1.9,1.4,3.2c0,1.3-0.5,2.4-1.4,3.3c-0.9,0.9-2,1.4-3.3,1.4C502.3,10.5,501.2,10.1,500.3,9.2z M500.7,24.8h5.6v58.9
|
||||
h-5.6V24.8z"/>
|
||||
<path class="st1" d="M559.4,80c-1.4,1.4-3.2,2.4-5.4,3.1c-2.1,0.7-4.4,1.1-6.7,1.1c-5.1,0-9.1-1.4-11.9-4.2
|
||||
c-2.8-2.8-4.2-6.8-4.2-11.8V29.7h-10.8v-4.9h10.8V12h5.6v12.9h18.7v4.9H537v37.9c0,3.8,0.9,6.8,2.8,8.8c1.8,2,4.6,3,8.2,3
|
||||
c3.7,0,6.7-1.1,9.1-3.3L559.4,80z"/>
|
||||
<path class="st1" d="M611.8,30c3.9,3.7,5.9,9.2,5.9,16.4v37.4h-5.4V73.3c-1.9,3.5-4.6,6.2-8.2,8.1c-3.6,1.9-7.9,2.9-13,2.9
|
||||
c-6.5,0-11.7-1.5-15.5-4.6c-3.8-3.1-5.7-7.1-5.7-12.2c0-4.9,1.8-8.9,5.2-11.9c3.5-3,9.1-4.6,16.8-4.6H612v-4.7
|
||||
c0-5.5-1.5-9.7-4.5-12.5c-3-2.9-7.3-4.3-13-4.3c-3.9,0-7.7,0.7-11.2,2c-3.6,1.4-6.6,3.2-9.1,5.4l-2.8-4.1c2.9-2.6,6.5-4.7,10.6-6.2
|
||||
c4.1-1.5,8.5-2.2,13-2.2C602.3,24.4,607.9,26.3,611.8,30z M604.3,76.2c3.4-2.3,6-5.5,7.7-9.8V55.3h-20.1c-5.8,0-10,1.1-12.6,3.2
|
||||
c-2.6,2.1-3.9,5-3.9,8.7c0,3.8,1.4,6.9,4.3,9.1c2.9,2.2,6.9,3.3,12.1,3.3C596.7,79.6,600.9,78.5,604.3,76.2z"/>
|
||||
<path class="st1" d="M640.9,0h5.6v83.8h-5.6V0z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.4 KiB |
13
.github/workflows/deploy.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Deployment to GitHub Pages
|
||||
name: Deployment to GH Pages
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
@@ -14,16 +14,15 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
node-version: 14
|
||||
- name: Setup Environment
|
||||
run: |
|
||||
npm install -g yarn
|
||||
yarn install
|
||||
npm install
|
||||
- name: Generate meta and builld
|
||||
run: |
|
||||
yarn meta
|
||||
yarn build
|
||||
- name: Deploy to GitHub Pages
|
||||
npm run meta
|
||||
npm run build
|
||||
- name: Deploy to GH Pages
|
||||
run: |
|
||||
git config user.email "kamranahmed.se@gmail.com"
|
||||
git config user.name "Kamran Ahmed"
|
||||
|
48
.gitignore
vendored
@@ -1,14 +1,36 @@
|
||||
_*
|
||||
Thumbs.db
|
||||
.DS_Store
|
||||
.idea
|
||||
.next
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
out
|
||||
.env
|
||||
build
|
||||
node_modules
|
||||
yarn-error.log
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
config/*.json
|
||||
!config/dev.json
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.idea
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
5
.prettierrc
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2
|
||||
}
|
@@ -73,4 +73,4 @@ available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.ht
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
https://www.contributor-covenant.org/faq
|
@@ -1,22 +0,0 @@
|
||||
import { AboutHeaderWrap } from './style';
|
||||
import siteConfig from "content/site";
|
||||
|
||||
const AboutHeader = () => (
|
||||
<AboutHeaderWrap>
|
||||
<div className="container container-small">
|
||||
<div className="author-info">
|
||||
<img className='author-img d-none d-sm-none d-md-block d-lg-block d-xl-block' src="/kamran.jpeg" />
|
||||
<div className="author-msg">
|
||||
<h1>Hello, I'm Kamran Ahmed.</h1>
|
||||
<p>I created <span className='flow-black'>roadmap.sh</span> to help developers find their path if they are confused and help them grow in their career.</p>
|
||||
<div className="author-links">
|
||||
<a href={`https://twitter.com/${siteConfig.twitter}`} target="_blank">@kamranahmedse</a>
|
||||
<a href="mailto:kamran@roadmap.sh">kamran@roadmap.sh</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AboutHeaderWrap>
|
||||
);
|
||||
|
||||
export default AboutHeader;
|
@@ -1,49 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const AboutHeaderWrap = styled.div`
|
||||
text-align: left;
|
||||
padding: 70px 0;
|
||||
margin: 0 auto;
|
||||
|
||||
.author-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 20px;
|
||||
color: #333;
|
||||
font-weight: 400;
|
||||
margin-bottom: 0;
|
||||
line-height: 35px;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 700;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.author-img {
|
||||
height: 160px;
|
||||
margin-right: 25px;
|
||||
margin-top: 8px;
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
.author-links a {
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
background: #2d2d2d;
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
font-weight: 500;
|
||||
margin-top: 12px;
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
@@ -1,54 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const BadgesList = styled.p`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 0;
|
||||
`;
|
||||
|
||||
export const PrimaryBadge = styled.span`
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
background: #2929ff;
|
||||
color: white;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
text-transform: uppercase;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 10px;
|
||||
|
||||
svg {
|
||||
height: 11px !important;
|
||||
margin-right: 5px;
|
||||
|
||||
&.fa-clock {
|
||||
height: 10px !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const SecondaryBadge = styled(PrimaryBadge)`
|
||||
background: #696969;
|
||||
color: white;
|
||||
`;
|
||||
|
||||
export const InfoBadge = styled(PrimaryBadge)`
|
||||
background: #039640;
|
||||
color: white;
|
||||
`;
|
||||
|
||||
export const DarkBadge = styled(PrimaryBadge)`
|
||||
background: #101010;
|
||||
color: white;
|
||||
`;
|
||||
|
||||
export const BadgeLink = styled.a`
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration:none;
|
||||
}
|
||||
`;
|
60
components/content-page-header.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Box, Container, Flex, Heading, Image, Link, Text } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
type ContentPageHeaderProps = {
|
||||
formattedDate: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
author?: {
|
||||
name: string;
|
||||
twitter: string;
|
||||
picture: string;
|
||||
},
|
||||
subLink?: {
|
||||
text: string;
|
||||
url: string;
|
||||
}
|
||||
};
|
||||
|
||||
export function ContentPageHeader(props: ContentPageHeaderProps) {
|
||||
const { title, subtitle, author = null, formattedDate, subLink = null } = props;
|
||||
|
||||
return (
|
||||
<Box pt={['35px', '35px', '70px']} pb={['35px', '35px', '55px']} borderBottomWidth={1} mb='30px'>
|
||||
<Container maxW='container.md' position='relative' textAlign={['left', 'left', 'center']}>
|
||||
<Flex alignItems='center' justifyContent={['flex-start', 'flex-start', 'center']}
|
||||
fontSize={['12px', '12px', '14px']}>
|
||||
|
||||
{author?.name && (
|
||||
<>
|
||||
<Link
|
||||
d={['none', 'flex', 'flex']}
|
||||
target='_blank'
|
||||
href={`https://twitter.com/${author.twitter}`}
|
||||
alignItems='center'
|
||||
fontWeight={600}
|
||||
color='gray.500'
|
||||
>
|
||||
<Image alt={''} rounded={'full'} mr='7px' w='22px' src={author.picture} />
|
||||
{author.name}
|
||||
</Link>
|
||||
<Text d={['none', 'inline', 'inline']} mx='7px' color='gray.500' as='span'>·</Text>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Text color='gray.500' as='span'>{formattedDate}</Text>
|
||||
{subLink?.text && (
|
||||
<>
|
||||
<Text d={['none', 'none', 'inline']} mx='7px' color='gray.500' as='span'>·</Text>
|
||||
<Link d={['none', 'none', 'inline']} color='blue.500' fontWeight={500}
|
||||
href={subLink.url} target={'_blank'}>{subLink.text}</Link>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
<Heading as='h1' color='black' fontSize={['30px', '30px', '45px']} lineHeight={['40px', '40px', '53px']}
|
||||
fontWeight={700} my={['5px', '5px', '10px']}>{title}</Heading>
|
||||
<Text fontSize={['14px', '14px', '16px']} color='gray.700'>{subtitle}</Text>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
37
components/custom-ad.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
export const CustomAd = () => {
|
||||
return (
|
||||
<div id='carbonads'>
|
||||
<span>
|
||||
<span className='carbon-wrap'>
|
||||
<a
|
||||
href='https://freemote.com/strategy?sl=roadmap'
|
||||
className='carbon-img'
|
||||
target='_blank'
|
||||
>
|
||||
<img
|
||||
src='/fm-img.png'
|
||||
alt='FM Logo'
|
||||
height='100'
|
||||
width='130'
|
||||
style={{ maxWidth: '130px', border: 'none' }}
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href='https://freemote.com/strategy?sl=roadmap'
|
||||
className='carbon-text'
|
||||
target='_blank'
|
||||
>
|
||||
He Went from ZERO TO $74,000 as a Full Time Developer in 7 Weeks
|
||||
</a>
|
||||
</span>
|
||||
<a
|
||||
href='https://github.com/sponsors/kamranahmedse'
|
||||
className='carbon-poweredby'
|
||||
target='_blank'
|
||||
>
|
||||
Sponsored by
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
@@ -1,133 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faFacebookSquare, faTwitterSquare, faRedditSquare, faGithubSquare } from '@fortawesome/free-brands-svg-icons'
|
||||
import {
|
||||
PageHeader,
|
||||
RoadmapMeta,
|
||||
ShareRoadmap,
|
||||
Sidebar,
|
||||
Summary,
|
||||
SummaryContainer,
|
||||
MobileNavHeader,
|
||||
SidebarButton,
|
||||
MobileSidebar,
|
||||
MobileSidebarWrap,
|
||||
DesktopSidebarWrap,
|
||||
PageTitle,
|
||||
PageDetail
|
||||
} from './style';
|
||||
import { faBars } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getFacebookShareUrl } from 'lib/url';
|
||||
import { ShareIcon } from 'components/share-icon';
|
||||
import { getRedditShareUrl, getTwitterShareUrl } from 'lib/url';
|
||||
import siteConfig from "content/site";
|
||||
import MdRenderer from 'components/md-renderer';
|
||||
|
||||
const DetailedRoadmap = ({ roadmap }) => {
|
||||
const [menuActive, setMenuState] = useState(false);
|
||||
const {
|
||||
sidebar = {},
|
||||
page: currentPage = {},
|
||||
author = {}
|
||||
} = roadmap;
|
||||
|
||||
const roadmapPages = Object.keys(sidebar || {}).map((groupTitle, groupCounter) => {
|
||||
if (groupTitle.startsWith('_')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @todo remove it after completing the frontend roadmap
|
||||
const isInProgress = groupCounter !== 0;
|
||||
|
||||
return (
|
||||
<div className={`links-group ${isInProgress ? 'in-progress' : ''}`} key={groupTitle}>
|
||||
<h3>
|
||||
{ groupTitle }
|
||||
{ isInProgress && <span className='badge badge-warning progress-badge'>In Progress</span> }
|
||||
</h3>
|
||||
<ul>
|
||||
{ sidebar[groupTitle].map(page => {
|
||||
const isActivePage = page.url === currentPage.url;
|
||||
// e.g. /frontend should mark `/frontend/landscape` as active
|
||||
const isSummaryPage = page.url === `${currentPage.url}/summary`;
|
||||
|
||||
return (
|
||||
<li className={classNames({ active: isActivePage || isSummaryPage })} key={page.url}>
|
||||
<a href={ page.url }>
|
||||
<span className="bullet"></span>
|
||||
{ page.title }
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}) }
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const filePath = currentPage.path.replace(/^\//, '');
|
||||
const RoadmapContent = require(`../../content/${filePath}`).default;
|
||||
|
||||
return (
|
||||
<SummaryContainer>
|
||||
<PageHeader className="border-top border-bottom text-center text-md-left">
|
||||
<div className="container d-flex align-items-center flex-column flex-md-row">
|
||||
<RoadmapMeta>
|
||||
<h3>{ roadmap.title }</h3>
|
||||
<p>
|
||||
Roadmap contributed by <a href={ author.url } target="_blank">{ author.name }</a>
|
||||
{ roadmap.contributorsCount > 1 && ` and <a href="${roadmap.contributorsUrl}">${roadmap.contributorsCount} others</a>`}
|
||||
</p>
|
||||
</RoadmapMeta>
|
||||
<ShareRoadmap className="mt-2 mt-md-0">
|
||||
<ShareIcon href={ siteConfig.url.repo } target="_blank">
|
||||
<FontAwesomeIcon icon={ faGithubSquare } />
|
||||
</ShareIcon>
|
||||
<ShareIcon href={ getFacebookShareUrl({ text: roadmap.title, url: roadmap.url }) } target="_blank">
|
||||
<FontAwesomeIcon icon={ faFacebookSquare } />
|
||||
</ShareIcon>
|
||||
<ShareIcon href={ getTwitterShareUrl({ text: roadmap.title, url: roadmap.url }) } target="_blank">
|
||||
<FontAwesomeIcon icon={ faTwitterSquare } />
|
||||
</ShareIcon>
|
||||
<ShareIcon href={ getRedditShareUrl({ text: roadmap.title, url: roadmap.url }) } target="_blank">
|
||||
<FontAwesomeIcon icon={ faRedditSquare } />
|
||||
</ShareIcon>
|
||||
</ShareRoadmap>
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<MobileNavHeader className="border-bottom d-block d-md-none">
|
||||
<div className="container">
|
||||
<SidebarButton onClick={() => setMenuState((prevMenuActive) => !prevMenuActive)}>
|
||||
<FontAwesomeIcon icon={ faBars } />
|
||||
{ currentPage.title }
|
||||
</SidebarButton>
|
||||
</div>
|
||||
<MobileSidebarWrap className={classNames({ visible: menuActive })}>
|
||||
<div className="container">
|
||||
<MobileSidebar>
|
||||
{ roadmapPages }
|
||||
</MobileSidebar>
|
||||
</div>
|
||||
</MobileSidebarWrap>
|
||||
</MobileNavHeader>
|
||||
|
||||
<Summary className="container">
|
||||
<DesktopSidebarWrap className="d-none d-md-block">
|
||||
<Sidebar>
|
||||
{ roadmapPages }
|
||||
</Sidebar>
|
||||
</DesktopSidebarWrap>
|
||||
<PageDetail>
|
||||
<PageTitle>{ currentPage.title }</PageTitle>
|
||||
<MdRenderer>
|
||||
<RoadmapContent />
|
||||
</MdRenderer>
|
||||
</PageDetail>
|
||||
</Summary>
|
||||
</SummaryContainer>
|
||||
)
|
||||
};
|
||||
|
||||
export default DetailedRoadmap;
|
@@ -1,199 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SummaryContainer = styled.div``;
|
||||
|
||||
export const Summary = styled.div`
|
||||
text-align: left;
|
||||
min-height: 400px;
|
||||
display: flex;
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
`;
|
||||
|
||||
export const PageHeader = styled.div`
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
|
||||
h3 {
|
||||
margin-bottom: 4px;
|
||||
font-weight: 600;
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
font-size: 14px;
|
||||
color: #696969;
|
||||
|
||||
a {
|
||||
color: #101010;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const RoadmapMeta = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
export const ShareRoadmap = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
a {
|
||||
margin-bottom: 0;
|
||||
& + a {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const MobileNavHeader = styled.div`
|
||||
padding: 10px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
export const SidebarButton = styled.button`
|
||||
background: transparent;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
outline: none !important;
|
||||
-webkit-appearance: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
svg {
|
||||
margin-right: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const PageDetail = styled.div`
|
||||
padding: 25px 0 100px;
|
||||
`;
|
||||
|
||||
export const PageTitle = styled.h1`
|
||||
font-size: 40px;
|
||||
font-weight: 700;
|
||||
`;
|
||||
|
||||
export const Sidebar = styled.div`
|
||||
padding-bottom: 150px;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.bullet {
|
||||
display: inline-block;
|
||||
margin-right: 7px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
min-width: 7px;
|
||||
border-radius: 100%;
|
||||
background: #efefef;
|
||||
transform: translateX(-4px);
|
||||
transition: background 0.5s ease;
|
||||
}
|
||||
|
||||
|
||||
.links-group {
|
||||
padding: 30px 0 10px;
|
||||
width: 260px;
|
||||
|
||||
h3 {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-badge {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.links-group.in-progress {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.links-group li {
|
||||
list-style: none;
|
||||
margin: 7px 0;
|
||||
|
||||
a {
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
color: #696969;
|
||||
}
|
||||
|
||||
.bullet {
|
||||
display: inline-block;
|
||||
margin-right: 12px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
min-width: 7px;
|
||||
border-radius: 100%;
|
||||
background: #efefef;
|
||||
transform: translateX(-4px);
|
||||
transition: background 0.5s ease;
|
||||
}
|
||||
|
||||
&.active a {
|
||||
color: #101010;
|
||||
}
|
||||
|
||||
&.active .bullet {
|
||||
background: #101010;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const DesktopSidebarWrap = styled.div`
|
||||
border-left: 1px solid #efefef;
|
||||
|
||||
${Sidebar} {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: white;
|
||||
}
|
||||
`;
|
||||
|
||||
export const MobileSidebarWrap = styled.div`
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
background: white;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
bottom: 100%;
|
||||
overflow-y: scroll;
|
||||
box-shadow: rgba(0, 0, 0, 0.1) 0 10px 20px;
|
||||
top: calc(100% + 1px);
|
||||
transition: bottom 0.5s ease 0s;
|
||||
|
||||
&.visible {
|
||||
bottom: -50vh;
|
||||
}
|
||||
`;
|
||||
|
||||
export const MobileSidebar = styled(Sidebar)`
|
||||
border-left: 1px solid #efefef;
|
||||
margin-left: 12px;
|
||||
padding-bottom: 20px;
|
||||
|
||||
.links-group {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.progress-badge {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
`;
|
46
components/dimmed-more.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Box, Link, Text } from '@chakra-ui/react';
|
||||
|
||||
type DimmedMoreProps = {
|
||||
text: string;
|
||||
href: string;
|
||||
};
|
||||
|
||||
export function DimmedMore(props: DimmedMoreProps) {
|
||||
const { text, href } = props;
|
||||
|
||||
return (
|
||||
<Box position='relative' textAlign='center' bottom='20px'>
|
||||
<Box
|
||||
opacity={1}
|
||||
pointerEvents='none'
|
||||
position='absolute'
|
||||
bottom={0}
|
||||
height='200px'
|
||||
width='100%'
|
||||
background='linear-gradient(180deg, rgb(255 255 255 / 40%), white)'
|
||||
/>
|
||||
|
||||
<Link
|
||||
rounded='20px'
|
||||
display='inline'
|
||||
bg='green.600'
|
||||
color='white'
|
||||
p='7px 20px'
|
||||
href={href}
|
||||
fontWeight={800}
|
||||
fontSize='11px'
|
||||
textTransform='uppercase'
|
||||
my='25px'
|
||||
position='relative'
|
||||
_hover={{
|
||||
textDecoration: 'none',
|
||||
'& .forward-arrow': {
|
||||
transform: 'translateX(3px)'
|
||||
}
|
||||
}}>
|
||||
{text}
|
||||
<Text d='inline-block' as='span' transition='200ms' ml='4px' className='forward-arrow'>→</Text>
|
||||
</Link>
|
||||
</Box>
|
||||
);
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
import { FaqContainer } from './style';
|
||||
import MdRenderer from 'components/md-renderer';
|
||||
|
||||
const AboutPage = require(`../../content/pages/about.md`).default;
|
||||
|
||||
const FaqList = () => (
|
||||
<FaqContainer className='border-top bg-light'>
|
||||
<div className="container container-small">
|
||||
<MdRenderer>
|
||||
<AboutPage />
|
||||
</MdRenderer>
|
||||
</div>
|
||||
</FaqContainer>
|
||||
);
|
||||
|
||||
export default FaqList;
|
@@ -1,25 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const FaqContainer = styled.div`
|
||||
padding: 40px 0;
|
||||
|
||||
h4 {
|
||||
margin-top: 30px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
h4:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
}
|
||||
`;
|
@@ -1,25 +0,0 @@
|
||||
import Link from 'next/link';
|
||||
import { FeaturedContentWrap } from './style';
|
||||
import FeaturedGuide from 'components/featured-guide';
|
||||
import { getFeaturedGuides } from 'lib/guide';
|
||||
|
||||
const FeaturedGuides = () => (
|
||||
<FeaturedContentWrap className="featured-content-wrap">
|
||||
<div className="container">
|
||||
<p className='border-through featured-separator'>
|
||||
<span>
|
||||
Guides mostly visited by the community
|
||||
<a href="/guides" className="dark-link d-none d-sm-none d-md-inline d-xl-inline">View all Guides →</a>
|
||||
</span>
|
||||
</p>
|
||||
<div className="swim-lane row">
|
||||
{ getFeaturedGuides()
|
||||
.map(guide => (
|
||||
<FeaturedGuide guide={ guide } key={ guide.url } />
|
||||
)) }
|
||||
</div>
|
||||
</div>
|
||||
</FeaturedContentWrap>
|
||||
);
|
||||
|
||||
export default FeaturedGuides;
|
@@ -1,16 +0,0 @@
|
||||
import { FeaturedWrap } from './style';
|
||||
import FeaturedGuides from './guides';
|
||||
import FeaturedRoadmaps from './roadmaps';
|
||||
|
||||
const FeaturedContent = (props) => (
|
||||
<FeaturedWrap className="border-top bg-light">
|
||||
<FeaturedRoadmaps />
|
||||
<FeaturedGuides />
|
||||
</FeaturedWrap>
|
||||
);
|
||||
|
||||
FeaturedContent.defaultProps = {
|
||||
className: '',
|
||||
};
|
||||
|
||||
export default FeaturedContent;
|
@@ -1,27 +0,0 @@
|
||||
import { FeaturedContentWrap } from './style';
|
||||
import roadmaps from 'content/roadmaps';
|
||||
import FeaturedRoadmap from 'components/featured-roadmap';
|
||||
|
||||
const FeaturedRoadmaps = () => (
|
||||
<FeaturedContentWrap className="featured-content-wrap">
|
||||
<div className="container">
|
||||
<div className="featured-head">
|
||||
<p className="border-through featured-separator">
|
||||
<span>
|
||||
Roadmaps mostly visited by the community
|
||||
<a href='/roadmaps' className="dark-link d-none d-sm-none d-md-inline d-xl-inline">View all Roadmaps →</a>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="swim-lane row">
|
||||
{ roadmaps
|
||||
.filter(({ featured }) => featured)
|
||||
.map(roadmap => (
|
||||
<FeaturedRoadmap roadmap={ roadmap } key={ roadmap.url } />
|
||||
)) }
|
||||
</div>
|
||||
</div>
|
||||
</FeaturedContentWrap>
|
||||
);
|
||||
|
||||
export default FeaturedRoadmaps;
|
@@ -1,110 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const FeaturedWrap = styled.div`
|
||||
padding: 30px 0 50px;
|
||||
`;
|
||||
|
||||
export const FeaturedContentWrap = styled.div`
|
||||
.featured-head {
|
||||
text-align: center;
|
||||
display: block;
|
||||
|
||||
h3 {
|
||||
font-weight: 700;
|
||||
font-size: 35px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.featured-separator {
|
||||
font-size: 16px;
|
||||
margin-bottom: 40px;
|
||||
margin-top: 25px;
|
||||
color: #999;
|
||||
|
||||
span {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
a {
|
||||
background: black;
|
||||
color: white;
|
||||
padding: 3px 8px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.swim-lane {
|
||||
.featured-block {
|
||||
}
|
||||
}
|
||||
|
||||
.guide-item {
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
padding: 15px 10px;
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
font-size: 15px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.journey-block {
|
||||
a {
|
||||
border: 1px solid #f7f7f7;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: #000000;
|
||||
background: #ffffff;
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 5px 10px;
|
||||
transition: box-shadow 0.2s ease 0s;
|
||||
cursor: pointer;
|
||||
margin-bottom: 32px;
|
||||
border-radius: 0 0 10px 10px;
|
||||
|
||||
&:hover {
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 30px 60px;
|
||||
|
||||
img {
|
||||
filter: grayscale(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.journey-meta {
|
||||
padding: 18px 25px 20px;
|
||||
|
||||
h4 {
|
||||
line-height: 27px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0;
|
||||
font-size: 18px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
flex: 0 0 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 15px;
|
||||
line-height: 25px;
|
||||
color: #999999;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
filter: grayscale(1);
|
||||
border-radius: 10px 10px 0 0;
|
||||
}
|
||||
}
|
||||
`;
|
@@ -1,25 +0,0 @@
|
||||
import formatDate from 'date-fns/format';
|
||||
|
||||
import { Author, AuthorImage, AuthorName, BlockLink, BlockMeta, BlockSubtitle, BlockTitle, PublishDate } from './style';
|
||||
import { findByUsername } from 'lib/author';
|
||||
|
||||
const FeaturedGuide = ({ guide }) => {
|
||||
const author = findByUsername(guide.author) || {};
|
||||
return (
|
||||
<div className='col-xl-4 col-lg-6 col-md-6 col-sm-12 col-12 grid-item-container'>
|
||||
<BlockLink href={guide.url}>
|
||||
<BlockTitle>{guide.title}</BlockTitle>
|
||||
<BlockSubtitle>{guide.featuredDescription || guide.description}</BlockSubtitle>
|
||||
<BlockMeta>
|
||||
<Author>
|
||||
<AuthorImage src={author.picture} />
|
||||
<AuthorName>{author.name}</AuthorName>
|
||||
</Author>
|
||||
<PublishDate>{formatDate(new Date(guide.createdAt), 'MMMM d, yyyy')}</PublishDate>
|
||||
</BlockMeta>
|
||||
</BlockLink>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeaturedGuide;
|
@@ -1,65 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const BlockLink = styled.a`
|
||||
border: 1px solid #f7f7f7;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: #000000;
|
||||
background: #ffffff;
|
||||
padding: 25px 25px 22px;
|
||||
border-radius: 10px;
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 5px 10px;
|
||||
transition: box-shadow 0.2s ease 0s;
|
||||
cursor: pointer;
|
||||
margin-bottom: 32px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
color: #000000;
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 30px 60px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const BlockTitle = styled.h4`
|
||||
line-height: 27px;
|
||||
font-weight: 600;
|
||||
font-size: 22px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
flex: 0 0 auto;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
export const BlockSubtitle = styled.p`
|
||||
font-size: 15px;
|
||||
line-height: 25px;
|
||||
color: #999999;
|
||||
margin-bottom: 0;
|
||||
`;
|
||||
|
||||
export const BlockMeta = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 19px;
|
||||
`;
|
||||
|
||||
export const PublishDate = styled.time`
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
`;
|
||||
|
||||
export const Author = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 15px;
|
||||
color: #999;
|
||||
`;
|
||||
|
||||
export const AuthorImage = styled.img`
|
||||
height: 22px;
|
||||
width: 22px;
|
||||
border-radius: 100%;
|
||||
margin-right: 10px;
|
||||
`;
|
||||
export const AuthorName = styled.div``;
|
@@ -1,12 +0,0 @@
|
||||
import { BlockLink, BlockSubtitle, BlockTitle } from './style';
|
||||
|
||||
const FeaturedRoadmap = ({ roadmap }) => (
|
||||
<div className='col-xl-4 col-lg-4 col-md-6 col-sm-12 col-12 grid-item-container'>
|
||||
<BlockLink href={roadmap.url}>
|
||||
<BlockTitle>{roadmap.title}</BlockTitle>
|
||||
<BlockSubtitle>{roadmap.featuredDescription || roadmap.description}</BlockSubtitle>
|
||||
</BlockLink>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default FeaturedRoadmap;
|
@@ -1,38 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const BlockLink = styled.a`
|
||||
border: 1px solid #f7f7f7;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: #000000;
|
||||
background: #ffffff;
|
||||
padding: 26px 25px 25px;
|
||||
border-radius: 10px;
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 5px 10px;
|
||||
transition: box-shadow 0.2s ease 0s;
|
||||
cursor: pointer;
|
||||
margin-bottom: 32px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
color: #000000;
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 30px 60px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const BlockTitle = styled.h4`
|
||||
line-height: 27px;
|
||||
font-weight: 600;
|
||||
font-size: 22px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
flex: 0 0 auto;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
export const BlockSubtitle = styled.p`
|
||||
font-size: 15px;
|
||||
line-height: 25px;
|
||||
color: #999999;
|
||||
margin-bottom: 0;
|
||||
`;
|
79
components/footer.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import { Box, Container, Flex, Image, Link, Stack, Text } from '@chakra-ui/react';
|
||||
import siteConfig from '../content/site.json';
|
||||
import { CustomAd } from './custom-ad';
|
||||
|
||||
function NavigationLinks() {
|
||||
return (
|
||||
<>
|
||||
<Stack isInline d={['none', 'none', 'flex']} color='gray.400' fontWeight={600} spacing='30px'>
|
||||
<Link _hover={{ color: 'white' }} href='/roadmaps'>Roadmaps</Link>
|
||||
<Link _hover={{ color: 'white' }} href='/guides'>Guides</Link>
|
||||
<Link _hover={{ color: 'white' }} href='/watch'>Videos</Link>
|
||||
<Link _hover={{ color: 'white' }} href='/about'>About</Link>
|
||||
<Link _hover={{ color: 'white' }} href={siteConfig.url.youtube} target='_blank'>YouTube</Link>
|
||||
</Stack>
|
||||
|
||||
<Stack d={['flex', 'flex', 'none']} color='gray.400' fontWeight={600} spacing={0}>
|
||||
<Link py='7px' borderBottomWidth={1} borderBottomColor='gray.800' _hover={{ color: 'white' }}
|
||||
href='/roadmaps'>Roadmaps</Link>
|
||||
<Link py='7px' borderBottomWidth={1} borderBottomColor='gray.800' _hover={{ color: 'white' }}
|
||||
href='/guides'>Guides</Link>
|
||||
<Link py='7px' borderBottomWidth={1} borderBottomColor='gray.800' _hover={{ color: 'white' }}
|
||||
href='/watch'>Videos</Link>
|
||||
<Link py='7px' borderBottomWidth={1} borderBottomColor='gray.800' _hover={{ color: 'white' }}
|
||||
href='/thanks'>Thanks</Link>
|
||||
<Link py='7px' borderBottomWidth={1} borderBottomColor='gray.800' _hover={{ color: 'white' }}
|
||||
href='/about'>About</Link>
|
||||
<Link py='7px' _hover={{ color: 'white' }} target='_blank'
|
||||
href={siteConfig.url.youtube}>YouTube</Link>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<Box bg='brand.hero' p={['25px 0', '25px 0', '40px 0']}>
|
||||
<Container maxW='container.md'>
|
||||
<NavigationLinks />
|
||||
|
||||
<Box mt={['40px', '40px', '50px']} mb='40px' maxW='500px'>
|
||||
<Flex spacing={0} alignItems='center' color='gray.400'>
|
||||
<Link d='flex' alignItems='center' fontWeight={600} _hover={{ textDecoration: 'none', color: 'white' }}
|
||||
href='/'>
|
||||
<Image alt='' h='25px' w='25px' src='/logo.svg' mr='6px' />
|
||||
roadmap.sh
|
||||
</Link>
|
||||
<Text as='span' mx='7px'>by</Text>
|
||||
<Link bg='blue.500' px='6px' py='2px' rounded='4px' color='white' fontWeight={600} fontSize='13px'
|
||||
_hover={{ textDecoration: 'none', bg: 'blue.600' }} href={siteConfig.url.twitter}
|
||||
target='_blank'>@kamranahmedse</Link>
|
||||
</Flex>
|
||||
|
||||
<Text my='15px' fontSize='14px' color='gray.500'>Community created roadmaps, articles, resources and
|
||||
journeys to help you choose your path and grow in your career.</Text>
|
||||
|
||||
<Text fontSize='14px' color='gray.500'>
|
||||
<Text as='span' mr='10px'>© roadmap.sh</Text>·
|
||||
<Link href='/about' _hover={{ textDecoration: 'none', color: 'white' }} color='gray.400'
|
||||
mx='10px'>FAQs</Link>·
|
||||
<Link href='/terms' _hover={{ textDecoration: 'none', color: 'white' }} color='gray.400'
|
||||
mx='10px'>Terms</Link>·
|
||||
<Link href='/privacy' _hover={{ textDecoration: 'none', color: 'white' }} color='gray.400'
|
||||
mx='10px'>Privacy</Link>
|
||||
</Text>
|
||||
</Box>
|
||||
</Container>
|
||||
|
||||
{/*<CustomAd />*/}
|
||||
{process.env.GA_SECRET && (
|
||||
<script
|
||||
async
|
||||
type='text/javascript'
|
||||
src='//cdn.carbonads.com/carbon.js?serve=CE7DLK3Y&placement=roadmapsh'
|
||||
id='_carbonads_js'
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
119
components/global-header.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import { useState } from 'react';
|
||||
import { HamburgerIcon } from '@chakra-ui/icons';
|
||||
import { Box, CloseButton, Container, Flex, IconButton, Image, Link, Stack, Text } from '@chakra-ui/react';
|
||||
import RoadmapLogo from '../components/icons/roadmap.svg';
|
||||
import siteConfig from '../content/site.json';
|
||||
|
||||
type MenuLinkProps = {
|
||||
text: string;
|
||||
link: string;
|
||||
};
|
||||
|
||||
function MenuLink(props: MenuLinkProps) {
|
||||
const { text, link } = props;
|
||||
|
||||
return <Link
|
||||
borderBottomWidth={0}
|
||||
borderBottomColor='gray.500'
|
||||
_hover={{ textDecoration: 'none', borderBottomColor: 'white' }}
|
||||
fontWeight={500}
|
||||
href={link}
|
||||
>
|
||||
{text}
|
||||
</Link>;
|
||||
}
|
||||
|
||||
function DesktopMenuLinks() {
|
||||
return (
|
||||
<Stack d={['none', 'flex', 'flex']} shouldWrapChildren isInline spacing='15px' alignItems='center' color='gray.50'
|
||||
fontSize='15px'>
|
||||
<MenuLink text={'Roadmaps'} link={'/roadmaps'} />
|
||||
<MenuLink text={'Guides'} link={'/guides'} />
|
||||
<MenuLink text={'Videos'} link={'/watch'} />
|
||||
<MenuLink text={'Thanks'} link={'/thanks'} />
|
||||
|
||||
<Link ml='10px' bgGradient='linear(to-l, yellow.700, red.600)' p='7px 10px' rounded='4px'
|
||||
_hover={{ textDecoration: 'none', bgGradient: 'linear(to-l, red.800, yellow.700)' }}
|
||||
fontWeight={500} href={'/signup'}>Subscribe</Link>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function MobileMenuLinks() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButton
|
||||
rounded='5px'
|
||||
padding={0}
|
||||
aria-label={'Menu'}
|
||||
d={['block', 'none', 'none']}
|
||||
icon={<HamburgerIcon color='white' w='25px' height='25px' />}
|
||||
color='white'
|
||||
cursor='pointer'
|
||||
h='auto'
|
||||
bg='transparent'
|
||||
_hover={{ bg: 'transparent' }}
|
||||
_active={{ bg: 'transparent' }}
|
||||
_focus={{ bg: 'transparent' }}
|
||||
onClick={() => setIsOpen(true)}
|
||||
/>
|
||||
|
||||
{isOpen && (
|
||||
<Stack color='gray.100'
|
||||
fontSize={['22px', '22px', '22px', '32px']}
|
||||
alignItems='center'
|
||||
justifyContent='center'
|
||||
pos='fixed'
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
top={0}
|
||||
bg='gray.900'
|
||||
spacing='12px'
|
||||
zIndex={999}
|
||||
>
|
||||
<Link href='/roadmaps'>Roadmaps</Link>
|
||||
<Link href='/guides'>Guides</Link>
|
||||
<Link href='/watch'>Videos</Link>
|
||||
<Link href='/thanks'>Thanks</Link>
|
||||
<Link href='/signup'>Subscribe</Link>
|
||||
<CloseButton onClick={() => setIsOpen(false)} pos='fixed' top='40px' right='15px' size='lg' />
|
||||
</Stack>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
type GlobalHeaderProps = {
|
||||
variant?: 'transparent' | 'solid'
|
||||
};
|
||||
|
||||
export function GlobalHeader(props: GlobalHeaderProps) {
|
||||
const { variant = 'solid' } = props;
|
||||
|
||||
return (
|
||||
<Box bg={variant === 'solid' ? 'gray.900' : 'transparent'} p='20px 0'>
|
||||
<Container maxW='container.md'>
|
||||
<Flex justifyContent='space-between' alignItems='center'>
|
||||
<Box>
|
||||
<Link w='100%'
|
||||
d='flex'
|
||||
href='/'
|
||||
alignItems='center'
|
||||
color='white'
|
||||
fontWeight={600}
|
||||
_hover={{ textDecoration: 'none' }}
|
||||
fontSize='18px'>
|
||||
<RoadmapLogo style={{ height: '30px', width: '30px', marginRight: '10px' }} />
|
||||
<Text d={['block', 'none', 'block']} as='span'>roadmap.sh</Text>
|
||||
</Link>
|
||||
</Box>
|
||||
<DesktopMenuLinks />
|
||||
<MobileMenuLinks />
|
||||
</Flex>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
import MdRenderer from 'components/md-renderer'
|
||||
import SharePage from 'components/share-page';
|
||||
import { GuideBodyWrap } from './style';
|
||||
|
||||
const GuideBody = ({ guide }) => {
|
||||
const GuideContent = require(`../../content/guides/${guide.fileName}.md`).default;
|
||||
return (
|
||||
<GuideBodyWrap>
|
||||
<MdRenderer>
|
||||
<GuideContent />
|
||||
{
|
||||
guide.author && <SharePage
|
||||
title={ guide.title }
|
||||
url={ guide.url }
|
||||
twitterUsername={ guide.author.twitter }
|
||||
/>
|
||||
}
|
||||
</MdRenderer>
|
||||
</GuideBodyWrap>
|
||||
);
|
||||
};
|
||||
|
||||
export default GuideBody;
|
@@ -1,13 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const GuideBodyWrap = styled.div`
|
||||
position: relative;
|
||||
max-width: 750px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
min-height: 300px;
|
||||
|
||||
p:first-child, h1, h2, h3, h4, h5, h6, img, blockquote {
|
||||
margin-top: 0;
|
||||
}
|
||||
`;
|
@@ -1,83 +0,0 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faFacebookSquare, faGithub, faHackerNewsSquare, faRedditSquare, faTwitter, faTwitterSquare } from '@fortawesome/free-brands-svg-icons'
|
||||
import { getContributionUrl } from "lib/guide";
|
||||
import {
|
||||
getTwitterUrl,
|
||||
getTwitterShareUrl,
|
||||
getFacebookShareUrl,
|
||||
getRedditShareUrl,
|
||||
getHnShareUrl
|
||||
} from "lib/url";
|
||||
import {
|
||||
AuthorBio,
|
||||
AuthorImg,
|
||||
AuthorInfoWrap,
|
||||
AuthorMeta,
|
||||
ContributeIcon,
|
||||
FooterBg,
|
||||
FooterContainer,
|
||||
FooterWrap,
|
||||
ShareIcons,
|
||||
ShareWrap
|
||||
} from './style';
|
||||
|
||||
|
||||
const GuideFooter = ({
|
||||
guide,
|
||||
guide: {
|
||||
author = {}
|
||||
} = {}
|
||||
}) => (
|
||||
<FooterWrap>
|
||||
<FooterBg className="border-top">
|
||||
<FooterContainer>
|
||||
<ShareWrap>
|
||||
<ContributeIcon>
|
||||
<a href={ getContributionUrl(guide) } target="_blank">
|
||||
<span className="d-none d-sm-none d-md-inline d-lg-inline d-xl-inline">Improve this Guide </span>
|
||||
<span className="d-inline d-sm-inline d-md-none d-lg-none d-xl-none">Contribute </span>
|
||||
<FontAwesomeIcon icon={faGithub}/>
|
||||
</a>
|
||||
</ContributeIcon>
|
||||
<ContributeIcon hasMargins>
|
||||
<a href={ getTwitterUrl(author.twitter) } target="_blank">
|
||||
<span className="d-none d-sm-none d-md-inline d-lg-inline d-xl-inline">Follow the author </span>
|
||||
<span className="d-inline d-sm-inline d-md-none d-lg-none d-xl-none">Author </span>
|
||||
<FontAwesomeIcon icon={faTwitter}/>
|
||||
</a>
|
||||
</ContributeIcon>
|
||||
<ShareIcons>
|
||||
<span className="d-none d-sm-none d-md-none d-lg-inline d-xl-inline">Help spread the word</span>
|
||||
<span className="d-inline d-sm-inline d-md-inline d-lg-none d-xl-none">Share</span>
|
||||
<a href={ getTwitterShareUrl({ text: `${guide.title} by @${author.twitter}`, url: guide.url })} target="_blank">
|
||||
<FontAwesomeIcon icon={faTwitterSquare}/>
|
||||
</a>
|
||||
<a href={ getFacebookShareUrl({ text: guide.title, url: guide.url }) } target="_blank">
|
||||
<FontAwesomeIcon icon={faFacebookSquare}/>
|
||||
</a>
|
||||
<a href={ getRedditShareUrl({ text: guide.title, url: guide.url })} target="_blank">
|
||||
<FontAwesomeIcon icon={faRedditSquare}/>
|
||||
</a>
|
||||
<a href={ getHnShareUrl({ text: guide.title, url: guide.url })} target="_blank">
|
||||
<FontAwesomeIcon icon={faHackerNewsSquare}/>
|
||||
</a>
|
||||
</ShareIcons>
|
||||
</ShareWrap>
|
||||
</FooterContainer>
|
||||
</FooterBg>
|
||||
|
||||
<FooterBg className="border-top">
|
||||
<FooterContainer>
|
||||
<AuthorInfoWrap>
|
||||
<AuthorImg src={ author.picture } alt={ author.name }/>
|
||||
<AuthorMeta>
|
||||
<h4><a href={ getTwitterUrl(author.twitter) } target="_blank">{ author.name }</a></h4>
|
||||
<AuthorBio>{ author.bio }</AuthorBio>
|
||||
</AuthorMeta>
|
||||
</AuthorInfoWrap>
|
||||
</FooterContainer>
|
||||
</FooterBg>
|
||||
</FooterWrap>
|
||||
);
|
||||
|
||||
export default GuideFooter;
|
@@ -1,123 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const FooterWrap = styled.div`
|
||||
display: block;
|
||||
margin-top: 50px;
|
||||
`;
|
||||
|
||||
export const FooterContainer = styled.div`
|
||||
max-width: 750px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
`;
|
||||
|
||||
export const FooterBg = styled.div`
|
||||
`;
|
||||
|
||||
export const ShareWrap = styled.div`
|
||||
padding: 17px 0px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #101010;
|
||||
|
||||
svg {
|
||||
height: 18px !important;
|
||||
width: 18px !important;
|
||||
color: #757575;
|
||||
margin-left: 7px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
color: #101010;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
export const ContributeIcon = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: ${props => props.hasMargins ? '0 30px' : '0'};
|
||||
|
||||
span {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #757575;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: #101010;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const ShareIcons = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #757575;
|
||||
font-size: 14px;
|
||||
|
||||
span {
|
||||
margin-right: 4px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AuthorInfoWrap = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 40px 0;
|
||||
|
||||
h4 {
|
||||
position: relative;
|
||||
font-size: 22px;
|
||||
margin: 10px 0;
|
||||
line-height: 17px;
|
||||
|
||||
a {
|
||||
font-weight: 700;
|
||||
color: #101010;
|
||||
}
|
||||
}
|
||||
|
||||
.author-description {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AuthorBio = styled.p`
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
color: #757575;
|
||||
margin-bottom: 0;
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AuthorImg = styled.img`
|
||||
border-radius: 100%;
|
||||
height: 100px !important;
|
||||
width: 100px !important;
|
||||
margin-right: 22px;
|
||||
display: block;
|
||||
`;
|
||||
|
||||
export const AuthorMeta = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
`;
|
@@ -1,57 +0,0 @@
|
||||
import formatDate from 'date-fns/format'
|
||||
import {
|
||||
ActionItems,
|
||||
AuthorImage,
|
||||
EditGuide,
|
||||
GuideAuthor,
|
||||
GuideDate,
|
||||
GuideMeta,
|
||||
GuideSubtitle,
|
||||
GuideTitle,
|
||||
HeaderWrap,
|
||||
} from './style';
|
||||
import { getContributionUrl } from "lib/guide";
|
||||
import { getTwitterUrl } from "lib/url";
|
||||
import { faClock, faEnvelope, faArrowLeft } from '@fortawesome/free-solid-svg-icons';
|
||||
import { BadgeLink, BadgesList, PrimaryBadge, SecondaryBadge, DarkBadge } from 'components/badges';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
const GuideHeader = ({
|
||||
guide,
|
||||
guide: {
|
||||
author = {}
|
||||
} = {}
|
||||
}) => (
|
||||
<HeaderWrap className="border-bottom">
|
||||
<GuideMeta>
|
||||
<GuideAuthor href={ getTwitterUrl(author.twitter) } target="_blank">
|
||||
<AuthorImage src={ author.picture } />
|
||||
{ author.name }
|
||||
</GuideAuthor>
|
||||
·
|
||||
<GuideDate>{ formatDate(new Date(guide.createdAt), 'EEEE, MMMM d yyyy') }</GuideDate>
|
||||
·
|
||||
<EditGuide href={ getContributionUrl(guide) } target="_blank">Improve this Guide</EditGuide>
|
||||
</GuideMeta>
|
||||
<GuideTitle>{ guide.title }</GuideTitle>
|
||||
<GuideSubtitle>{ guide.description }</GuideSubtitle>
|
||||
<ActionItems>
|
||||
<BadgesList className="mt-4">
|
||||
<BadgeLink href="/guides">
|
||||
<SecondaryBadge>
|
||||
<FontAwesomeIcon icon={faArrowLeft}/>
|
||||
Other Guides
|
||||
</SecondaryBadge>
|
||||
</BadgeLink>
|
||||
<BadgeLink href="/signup">
|
||||
<PrimaryBadge>
|
||||
<FontAwesomeIcon icon={faEnvelope}/>
|
||||
Send me Updates
|
||||
</PrimaryBadge>
|
||||
</BadgeLink>
|
||||
</BadgesList>
|
||||
</ActionItems>
|
||||
</HeaderWrap>
|
||||
);
|
||||
|
||||
export default GuideHeader;
|
@@ -1,54 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const HeaderWrap = styled.div`
|
||||
padding: 80px 15px 45px;
|
||||
text-align: center;
|
||||
margin-bottom: 35px;
|
||||
`;
|
||||
|
||||
export const GuideTitle = styled.h1`
|
||||
font-weight: 700;
|
||||
font-size: 46px;
|
||||
margin: 12px 0;
|
||||
`;
|
||||
|
||||
export const GuideSubtitle = styled.p`
|
||||
margin-bottom: 0;
|
||||
font-size: 16px;
|
||||
color: #444;
|
||||
`;
|
||||
|
||||
export const GuideMeta = styled.p`
|
||||
margin-bottom: 0;
|
||||
color: #757575;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
`;
|
||||
|
||||
export const GuideDate = styled.span`
|
||||
margin-left: 7px;
|
||||
margin-right: 7px;
|
||||
`;
|
||||
|
||||
export const GuideAuthor = styled.a`
|
||||
margin-right: 7px;
|
||||
font-weight: 500;
|
||||
color: #101010;
|
||||
|
||||
&:hover {
|
||||
color: #101010;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AuthorImage = styled.img`
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 100%;
|
||||
margin-right: 10px;
|
||||
`;
|
||||
|
||||
export const EditGuide = styled.a`
|
||||
margin-left: 7px;
|
||||
`;
|
||||
|
||||
export const ActionItems = styled.div``;
|
31
components/guide/guide-grid-item.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Badge, Box, Heading, Link, Text } from '@chakra-ui/react';
|
||||
|
||||
type GuideGridItemProps = {
|
||||
title: string;
|
||||
href: string;
|
||||
subtitle: string;
|
||||
date: string;
|
||||
isNew?: boolean;
|
||||
colorIndex?: number;
|
||||
};
|
||||
|
||||
const bgColorList = [
|
||||
'gray.700',
|
||||
'purple.800'
|
||||
];
|
||||
|
||||
export function GuideGridItem(props: GuideGridItemProps) {
|
||||
const { title, subtitle, date, isNew = false, colorIndex = 0, href } = props;
|
||||
|
||||
return (
|
||||
<Box _hover={{ textDecoration: 'none', transform: 'scale(1.02)' }} as={Link} href={href} shadow='xl' p='20px'
|
||||
rounded='10px' bg={bgColorList[colorIndex] ?? bgColorList[0]} flex={1}>
|
||||
<Text mb='10px' fontSize='13px' color='gray.400'>
|
||||
{isNew && <Badge colorScheme={'yellow'} mr='10px'>New</Badge>}
|
||||
{date}
|
||||
</Text>
|
||||
<Heading color='white' mb={'6px'} fontSize='20px'>{title}</Heading>
|
||||
<Text color='gray.300' fontSize='14px'>{subtitle}</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
73
components/helmet.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import NextHead from 'next/head';
|
||||
import siteConfig from '../content/site.json';
|
||||
|
||||
type HelmetProps = {
|
||||
title?: string;
|
||||
keywords?: string[];
|
||||
canonical?: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
const Helmet = (props: HelmetProps) => (
|
||||
<NextHead>
|
||||
<meta charSet='UTF-8' />
|
||||
|
||||
<title>{props.title || siteConfig.title}</title>
|
||||
<meta name='description' content={props.description || siteConfig.description} />
|
||||
|
||||
<meta name='author' content={siteConfig.author} />
|
||||
<meta name='keywords' content={props.keywords ? props.keywords.join(',') : siteConfig.keywords.join(',')} />
|
||||
|
||||
<meta name='viewport'
|
||||
content='width=device-width, user-scalable=yes, initial-scale=1.0, maximum-scale=3.0, minimum-scale=1.0' />
|
||||
{props.canonical && <link rel='canonical' href={props.canonical} />}
|
||||
<meta httpEquiv='Content-Language' content='en' />
|
||||
|
||||
<meta property='og:title' content={props.title || siteConfig.title} />
|
||||
<meta property='og:description' content={props.description || siteConfig.description} />
|
||||
<meta property='og:image' content={`${siteConfig.url.web}${siteConfig.logoSquare}`} />
|
||||
<meta property='og:url' content={siteConfig.url.web} />
|
||||
<meta property='og:type' content='website' />
|
||||
<meta property='article:publisher' content={`https://facebook.com/${siteConfig.facebook}`} />
|
||||
<meta property='og:site_name' content={siteConfig.name} />
|
||||
<meta property='article:author' content={siteConfig.author} />
|
||||
|
||||
<meta name='twitter:card' content='summary' />
|
||||
<meta name='twitter:site' content={`@${siteConfig.twitter}`} />
|
||||
<meta name='twitter:title' content={props.title || siteConfig.title} />
|
||||
<meta name='twitter:description' content={props.description || siteConfig.description} />
|
||||
<meta name='twitter:image' content={`${siteConfig.url.web}${siteConfig.logoSquare}`} />
|
||||
<meta name='twitter:image:alt' content='roadmap.sh' />
|
||||
|
||||
<meta name='mobile-web-app-capable' content='yes' />
|
||||
<meta name='apple-mobile-web-app-capable' content='yes' />
|
||||
<meta name='apple-mobile-web-app-status-bar-style' content='black-translucent' />
|
||||
<link rel='apple-touch-icon' sizes='180x180' href='/manifest/apple-touch-icon.png' />
|
||||
<meta name='msapplication-TileColor' content='#101010' />
|
||||
<meta name='theme-color' content='#848a9a' />
|
||||
|
||||
<link rel='manifest' href='/manifest/manifest.json' />
|
||||
<link rel='icon' type='image/png' sizes='32x32' href='/manifest/icon32.png' />
|
||||
<link rel='icon' type='image/png' sizes='16x16' href='/manifest/icon16.png' />
|
||||
<link rel='shortcut icon' href='/manifest/favicon.ico' type='image/x-icon' />
|
||||
<link rel='icon' href='/manifest/favicon.ico' type='image/x-icon' />
|
||||
|
||||
{ /* Global Site Tag (gtag.js) - Google Analytics */}
|
||||
{process.env.GA_SECRET && (
|
||||
<>
|
||||
<script async src={`https://www.googletagmanager.com/gtag/js?id=${process.env.GA_SECRET}`} />
|
||||
<script dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', '${process.env.GA_SECRET}');
|
||||
`
|
||||
}} />
|
||||
</>
|
||||
)}
|
||||
|
||||
</NextHead>
|
||||
);
|
||||
|
||||
export default Helmet;
|
@@ -1,74 +0,0 @@
|
||||
import NextHead from 'next/head';
|
||||
import siteConfig from 'content/site';
|
||||
|
||||
const prepareTitle = (givenTitle) => {
|
||||
return givenTitle || siteConfig.title;
|
||||
};
|
||||
|
||||
const prepareDescription = (givenDescription) => {
|
||||
return givenDescription || siteConfig.description;
|
||||
};
|
||||
|
||||
// noinspection JSUnresolvedLibraryURL
|
||||
const Helmet = (props) => (
|
||||
<NextHead>
|
||||
<meta charSet='UTF-8' />
|
||||
|
||||
<title>{ prepareTitle(props.title) }</title>
|
||||
<meta name='description' content={ prepareDescription(props.description) } />
|
||||
|
||||
<meta name="author" content={ siteConfig.author } />
|
||||
<meta name="keywords" content={ props.keywords ? props.keywords.join(',') : siteConfig.keywords.join(',') } />
|
||||
|
||||
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1.0, maximum-scale=3.0, minimum-scale=1.0" />
|
||||
{ props.canonical && <link rel="canonical" href={ props.canonical } /> }
|
||||
<meta httpEquiv="Content-Language" content="en" />
|
||||
|
||||
<meta property="og:title" content={ prepareTitle(props.title) } />
|
||||
<meta property="og:description" content={ prepareDescription(props.description) } />
|
||||
<meta property="og:image" content={ `${siteConfig.url.web}${siteConfig.logoSquare}` } />
|
||||
<meta property="og:url" content={ siteConfig.url.web } />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="article:publisher" content={ `https://facebook.com/${siteConfig.facebook}` } />
|
||||
<meta property="og:site_name" content={ siteConfig.name } />
|
||||
<meta property="article:author" content={ siteConfig.author } />
|
||||
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:site" content={ `@${siteConfig.twitter}` } />
|
||||
<meta name="twitter:title" content={ prepareTitle(props.title) } />
|
||||
<meta name="twitter:description" content={ prepareDescription(props.description) } />
|
||||
<meta name="twitter:image" content={ `${siteConfig.url.web}${siteConfig.logoSquare}` } />
|
||||
<meta name="twitter:image:alt" content="roadmap.sh" />
|
||||
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/manifest/apple-touch-icon.png" />
|
||||
<meta name="msapplication-TileColor" content="#101010" />
|
||||
<meta name="theme-color" content="#848a9a" />
|
||||
|
||||
<link rel="manifest" href="/manifest/manifest.json" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/manifest/icon32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/manifest/icon16.png" />
|
||||
<link rel="shortcut icon" href="/manifest/favicon.ico" type="image/x-icon" />
|
||||
<link rel="icon" href="/manifest/favicon.ico" type="image/x-icon" />
|
||||
|
||||
{ /* Global Site Tag (gtag.js) - Google Analytics */ }
|
||||
{ process.env.GA_SECRET && (
|
||||
<>
|
||||
<script async src={ `https://www.googletagmanager.com/gtag/js?id=${process.env.GA_SECRET}` } />
|
||||
<script dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', '${process.env.GA_SECRET}');
|
||||
`,
|
||||
}} />
|
||||
</>
|
||||
)}
|
||||
|
||||
</NextHead>
|
||||
);
|
||||
|
||||
export default Helmet;
|
@@ -1,13 +0,0 @@
|
||||
import Link from 'next/link';
|
||||
import { HeroSectionWrap } from './style';
|
||||
|
||||
const HeroSection = () => (
|
||||
<HeroSectionWrap>
|
||||
<div className="container">
|
||||
<h1>Developer Roadmaps</h1>
|
||||
<p>Community driven roadmaps, articles and resources for developers. <Link href="/signup"><a>Sign up</a></Link> for occasional updates on new roadmaps, updates and guides</p>
|
||||
</div>
|
||||
</HeroSectionWrap>
|
||||
);
|
||||
|
||||
export default HeroSection;
|
@@ -1,30 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const HeroSectionWrap = styled.div`
|
||||
text-align: center;
|
||||
padding: 70px 20px;
|
||||
margin: 0 auto;
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 70px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 20px;
|
||||
color: #333;
|
||||
font-weight: 400;
|
||||
margin-bottom: 0;
|
||||
line-height: 35px;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 700;
|
||||
color: #000;
|
||||
}
|
||||
`;
|
@@ -1,15 +0,0 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import RowBlock from 'components/row-block';
|
||||
import { SubmitText, SubmitWrap } from './style';
|
||||
|
||||
const IconRowBlock = ({ url, icon, text, openExternal=false }) => (
|
||||
<RowBlock url={ url } openExternal={openExternal}>
|
||||
<SubmitWrap>
|
||||
<FontAwesomeIcon icon={ icon } />
|
||||
<SubmitText>{ text }</SubmitText>
|
||||
</SubmitWrap>
|
||||
</RowBlock>
|
||||
);
|
||||
|
||||
export default IconRowBlock;
|
@@ -1,20 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SubmitWrap = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
svg {
|
||||
height: 25px;
|
||||
color: #d6d6d6;
|
||||
}
|
||||
`;
|
||||
|
||||
export const SubmitText = styled.p`
|
||||
margin-bottom: 0;
|
||||
color: dimgrey;
|
||||
margin-top: 8px;
|
||||
`;
|
@@ -1,4 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-label="GitHub" role="img" viewBox="0 0 512 512" width="22px" height="20px">
|
||||
<rect width="512" height="512" rx="15%"/>
|
||||
<path fill="#fff" d="M335 499c14 0 12 17 12 17H165s-2-17 12-17c13 0 16-6 16-12l-1-50c-71 16-86-28-86-28-12-30-28-37-28-37-24-16 1-16 1-16 26 2 40 26 40 26 22 39 59 28 74 22 2-17 9-28 16-35-57-6-116-28-116-126 0-28 10-51 26-69-3-6-11-32 3-67 0 0 21-7 70 26 42-12 86-12 128 0 49-33 70-26 70-26 14 35 6 61 3 67 16 18 26 41 26 69 0 98-60 120-117 126 10 8 18 24 18 48l-1 70c0 6 3 12 16 12z"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 565 B After Width: | Height: | Size: 841 B |
4
components/icons/roadmap.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="30" height="30" viewBox="0 0 283 283" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 39C0 17.4609 17.4609 0 39 0H244C265.539 0 283 17.4609 283 39V244C283 265.539 265.539 283 244 283H39C17.4609 283 0 265.539 0 244V39Z" fill="black"></path>
|
||||
<path d="M121.215 210.72C119.348 211.28 116.361 211.84 112.255 212.4C108.335 212.96 104.228 213.24 99.9347 213.24C95.828 213.24 92.0947 212.96 88.7347 212.4C85.5614 211.84 82.8547 210.72 80.6147 209.04C78.3747 207.36 76.6014 205.12 75.2947 202.32C74.1747 199.333 73.6147 195.507 73.6147 190.84V106.84C73.6147 102.547 74.3614 98.9067 75.8547 95.92C77.5347 92.7467 79.868 89.9467 82.8547 87.52C85.8414 85.0933 89.4814 82.9467 93.7747 81.08C98.2547 79.0267 103.015 77.2533 108.055 75.76C113.095 74.2667 118.321 73.1467 123.735 72.4C129.148 71.4667 134.561 71 139.975 71C148.935 71 156.028 72.7733 161.255 76.32C166.481 79.68 169.095 85.28 169.095 93.12C169.095 95.7333 168.721 98.3467 167.975 100.96C167.228 103.387 166.295 105.627 165.175 107.68C161.255 107.68 157.241 107.867 153.135 108.24C149.028 108.613 145.015 109.173 141.095 109.92C137.175 110.667 133.441 111.507 129.895 112.44C126.535 113.187 123.641 114.12 121.215 115.24V210.72ZM166.387 188.32C166.387 180.48 168.813 173.947 173.667 168.72C178.52 163.493 185.147 160.88 193.547 160.88C201.947 160.88 208.573 163.493 213.427 168.72C218.28 173.947 220.707 180.48 220.707 188.32C220.707 196.16 218.28 202.693 213.427 207.92C208.573 213.147 201.947 215.76 193.547 215.76C185.147 215.76 178.52 213.147 173.667 207.92C168.813 202.693 166.387 196.16 166.387 188.32Z" fill="white"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
@@ -1,3 +1,3 @@
|
||||
<svg width="29" height="29">
|
||||
<svg width="29" height="29" fill="currentColor">
|
||||
<path d="M22.05 7.54a4.47 4.47 0 0 0-3.3-1.46 4.53 4.53 0 0 0-4.53 4.53c0 .35.04.7.08 1.05A12.9 12.9 0 0 1 5 6.89a5.1 5.1 0 0 0-.65 2.26c.03 1.6.83 2.99 2.02 3.79a4.3 4.3 0 0 1-2.02-.57v.08a4.55 4.55 0 0 0 3.63 4.44c-.4.08-.8.13-1.21.16l-.81-.08a4.54 4.54 0 0 0 4.2 3.15 9.56 9.56 0 0 1-5.66 1.94l-1.05-.08c2 1.27 4.38 2.02 6.94 2.02 8.3 0 12.86-6.9 12.84-12.85.02-.24 0-.43 0-.65a8.68 8.68 0 0 0 2.26-2.34c-.82.38-1.7.62-2.6.72a4.37 4.37 0 0 0 1.95-2.51c-.84.53-1.81.9-2.83 1.13z"></path>
|
||||
</svg>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 529 B After Width: | Height: | Size: 550 B |
21
components/icons/video-icon.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
export function VideoIcon(props: any) {
|
||||
return (
|
||||
<svg
|
||||
stroke='currentColor'
|
||||
fill='currentColor'
|
||||
strokeWidth='0'
|
||||
viewBox='0 0 24 24'
|
||||
height='1em'
|
||||
width='1em'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
{...props}
|
||||
>
|
||||
<g>
|
||||
<path fill='none' d='M0 0h24v24H0z' />
|
||||
<path
|
||||
d='M3 3.993C3 3.445 3.445 3 3.993 3h16.014c.548 0 .993.445.993.993v16.014a.994.994 0 0 1-.993.993H3.993A.994.994 0 0 1 3 20.007V3.993zm7.622 4.422a.4.4 0 0 0-.622.332v6.506a.4.4 0 0 0 .622.332l4.879-3.252a.4.4 0 0 0 0-.666l-4.88-3.252z'
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
3
components/icons/youtube.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill='currentColor'>
|
||||
<path d="M19.615 3.184c-3.604-.246-11.631-.245-15.23 0-3.897.266-4.356 2.62-4.385 8.816.029 6.185.484 8.549 4.385 8.816 3.6.245 11.626.246 15.23 0 3.897-.266 4.356-2.62 4.385-8.816-.029-6.185-.484-8.549-4.385-8.816zm-10.615 12.816v-8l8 3.993-8 4.007z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 369 B |
@@ -1,9 +0,0 @@
|
||||
export function BadgeLink({ target='_blank', variant ='success', badgeText, href, children }) {
|
||||
return (
|
||||
<p className='mb-0'>
|
||||
<a href={href} target={ target }>
|
||||
<span style={{ position: 'relative', top: '-2px'}} className={`badge badge-${variant}`}>{badgeText}</span> { children }
|
||||
</a>
|
||||
</p>
|
||||
);
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const StrongLink = styled.a`
|
||||
border-bottom: 2px solid currentColor;
|
||||
position: relative;
|
||||
transition: background-color 120ms;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
58
components/links-list-item.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import { Badge, Flex, Link, Text } from '@chakra-ui/react';
|
||||
|
||||
type LinksListItemProps = {
|
||||
href: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
badgeText?: string;
|
||||
target?: string;
|
||||
icon?: React.ReactChild;
|
||||
hideSubtitleOnMobile?: boolean;
|
||||
};
|
||||
|
||||
export function LinksListItem(props: LinksListItemProps) {
|
||||
const { title, subtitle, badgeText, icon, hideSubtitleOnMobile = false, href, target } = props;
|
||||
|
||||
return (
|
||||
<Link
|
||||
target={target || '_self'}
|
||||
href={href}
|
||||
fontSize={['14px', '14px', '15px']}
|
||||
py='9px'
|
||||
d='flex'
|
||||
flexDirection={['column', 'row', 'row']}
|
||||
fontWeight={500}
|
||||
color='gray.600'
|
||||
alignItems={['flex-start', 'center']}
|
||||
justifyContent={'space-between'}
|
||||
sx={{
|
||||
'@media (hover: none)': {
|
||||
'&:hover': {
|
||||
'& .list-item-title': {
|
||||
transform: 'none'
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
_hover={{
|
||||
textDecoration: 'none',
|
||||
color: 'blue.400',
|
||||
'& .list-item-title': {
|
||||
transform: 'translateX(10px)'
|
||||
}
|
||||
}}
|
||||
isTruncated
|
||||
maxWidth='100%'
|
||||
>
|
||||
<Flex alignItems='center' className='list-item-title' transition={'200ms'}>
|
||||
{icon}
|
||||
<Text maxWidth={'345px'} isTruncated as='span'>{title}</Text>
|
||||
{badgeText &&
|
||||
<Badge pos='relative' top='1px' variant='subtle' colorScheme='purple' ml='10px'>{badgeText}</Badge>}
|
||||
</Flex>
|
||||
<Text d={[hideSubtitleOnMobile ? 'none' : 'inline', 'inline']} mt={['3px', 0]} as='span'
|
||||
fontSize={['11px', '11px', '12px']} color='gray.500'>{subtitle}</Text>
|
||||
</Link>
|
||||
);
|
||||
}
|
21
components/links-list.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { StackDivider, VStack } from '@chakra-ui/react';
|
||||
|
||||
type LinksListProps = {
|
||||
children: React.ReactNode
|
||||
};
|
||||
|
||||
export function LinksList(props: LinksListProps) {
|
||||
const { children } = props;
|
||||
|
||||
return (
|
||||
<VStack
|
||||
rounded='5px'
|
||||
divider={<StackDivider borderColor='gray.200' />}
|
||||
spacing={0}
|
||||
align='stretch'
|
||||
>
|
||||
{children}
|
||||
</VStack>
|
||||
);
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Mark = styled.span`
|
||||
background: #1b1e21;
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0 4px;
|
||||
padding: 0 6px 0 7px;
|
||||
`;
|
@@ -1,10 +0,0 @@
|
||||
import { MDXProvider } from '@mdx-js/react';
|
||||
import MdxComponents from './mdx-components';
|
||||
|
||||
const MdRenderer = (props) => (
|
||||
<MDXProvider components={ MdxComponents }>
|
||||
{ props.children }
|
||||
</MDXProvider>
|
||||
);
|
||||
|
||||
export default MdRenderer;
|
20
components/md-renderer/index.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
// @ts-ignore
|
||||
import { MDXProvider } from '@mdx-js/react';
|
||||
import { ChakraProvider } from '@chakra-ui/react';
|
||||
import MdxComponents from './mdx-components';
|
||||
import { roadmapTheme } from '../../styles/theme';
|
||||
|
||||
type MdRendererType = {
|
||||
children: React.ReactNode
|
||||
};
|
||||
|
||||
export default function MdRenderer(props: MdRendererType) {
|
||||
return (
|
||||
<ChakraProvider theme={roadmapTheme} resetCSS>
|
||||
<MDXProvider components={MdxComponents}>
|
||||
{props.children}
|
||||
</MDXProvider>
|
||||
</ChakraProvider>
|
||||
);
|
||||
};
|
@@ -1,18 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Link = styled.a`
|
||||
font-weight: 600;
|
||||
`;
|
||||
|
||||
const EnrichedLink = props => {
|
||||
// Is external URL or is a media URL
|
||||
const isExternalUrl = /(^http(s)?:\/\/)|(\.(png|svg|jpeg|jpg)$)/.test(props.href);
|
||||
|
||||
return (
|
||||
<Link href={ props.href } target={ isExternalUrl ? '_blank' : '_self' }>
|
||||
{ props.children }
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnrichedLink;
|
26
components/md-renderer/mdx-components/a.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
type EnrichedLinkProps = {
|
||||
href: string;
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const Link = styled.a`
|
||||
font-weight: 600;
|
||||
text-decoration: underline;
|
||||
`;
|
||||
|
||||
const EnrichedLink = (props: EnrichedLinkProps) => {
|
||||
// Is external URL or is a media URL
|
||||
const isExternalUrl = /(^http(s)?:\/\/)|(\.(png|svg|jpeg|jpg)$)/.test(props.href);
|
||||
|
||||
return (
|
||||
<Link href={props.href} target={isExternalUrl ? '_blank' : '_self'}>
|
||||
{props.children}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnrichedLink;
|
||||
|
23
components/md-renderer/mdx-components/badge-link.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import { Link, Text, Badge } from '@chakra-ui/react';
|
||||
|
||||
type BadgeLinkType = {
|
||||
target: string;
|
||||
badgeText: string;
|
||||
href: string;
|
||||
colorScheme?: string;
|
||||
children: React.ReactNode
|
||||
};
|
||||
|
||||
export function BadgeLink(props: BadgeLinkType) {
|
||||
const { target = '_blank', colorScheme='purple', badgeText, href, children } = props;
|
||||
|
||||
return (
|
||||
<Text mb={'0px'}>
|
||||
<Link fontSize='14px' color='blue.700' fontWeight={500} textDecoration='none' href={href} target={target} _hover={{ textDecoration: 'none', color: 'purple.400' }}>
|
||||
<Badge fontSize='11px' mr='7px' colorScheme={colorScheme}>{badgeText}</Badge>
|
||||
{children}
|
||||
</Link>
|
||||
</Text>
|
||||
);
|
||||
}
|
@@ -5,20 +5,21 @@ const BlockQuote = styled.blockquote`
|
||||
position: relative;
|
||||
background: #e8e8e8;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 18px;
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
|
||||
p + h4 {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
|
||||
|
||||
& + p {
|
||||
margin-top: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
`;
|
10
components/md-renderer/mdx-components/code.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Code as ChakraCode } from '@chakra-ui/react';
|
||||
|
||||
type CodeType = {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function Code(props: CodeType) {
|
||||
return <ChakraCode bg='blue.500'>{props.children}</ChakraCode>;
|
||||
}
|
@@ -1,21 +1,22 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import Link from 'components/icons/link.svg';
|
||||
import LinkIcon from 'components/icons/link.svg';
|
||||
|
||||
const linkify = (Component) => {
|
||||
return (props) => {
|
||||
const linkify = (Component: React.FunctionComponent<any>) => {
|
||||
return function EnrichedHeading(props: { children: string }): React.ReactNode {
|
||||
const text = props.children;
|
||||
const id = text.toLowerCase && text
|
||||
const id = text?.toLowerCase && text
|
||||
.toLowerCase()
|
||||
.replace(/[^\x00-\x7F]/g, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/[?!]/g, '');
|
||||
|
||||
return (
|
||||
<Component id={ id }>
|
||||
<HeaderLink href={ `#${id}` }>
|
||||
<Link />
|
||||
<Component id={id}>
|
||||
<HeaderLink href={`#${id}`}>
|
||||
<LinkIcon />
|
||||
</HeaderLink>
|
||||
{ props.children }
|
||||
{props.children}
|
||||
</Component>
|
||||
);
|
||||
};
|
||||
@@ -34,22 +35,23 @@ const HeaderLink = styled.a`
|
||||
|
||||
const H1 = styled.h1`
|
||||
position: relative;
|
||||
font-size: 42px;
|
||||
font-size: 32px;
|
||||
line-height: 40px;
|
||||
font-weight: 700;
|
||||
margin: 32px 0 10px !important;
|
||||
|
||||
margin: 20px 0 10px !important;
|
||||
|
||||
&:hover ${HeaderLink} {
|
||||
display: flex;
|
||||
}
|
||||
`;
|
||||
|
||||
const H2 = styled(H1).attrs({ as: 'h2' })`
|
||||
font-size: 32px;
|
||||
font-size: 30px;
|
||||
`;
|
||||
|
||||
const H3 = styled(H1).attrs({ as: 'h3' })`
|
||||
margin: 22px 0 8px;
|
||||
font-size: 30px;
|
||||
font-size: 28px;
|
||||
`;
|
||||
|
||||
const H4 = styled(H1).attrs({ as: 'h4' })`
|
||||
@@ -67,11 +69,13 @@ const H6 = styled(H1).attrs({ as: 'h6' })`
|
||||
font-size: 18px;
|
||||
`;
|
||||
|
||||
export const Headings = {
|
||||
h1: linkify(H1),
|
||||
h2: linkify(H2),
|
||||
h3: linkify(H3),
|
||||
h4: linkify(H4),
|
||||
h5: linkify(H5),
|
||||
h6: linkify(H6),
|
||||
const Headings = {
|
||||
h1: H1,
|
||||
h2: H2,
|
||||
h3: H3,
|
||||
h4: H4,
|
||||
h5: H5,
|
||||
h6: H6
|
||||
};
|
||||
|
||||
export default Headings;
|
@@ -1,8 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const IFrame = styled.iframe`
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: none;
|
||||
margin: 30px auto;
|
||||
`;
|
47
components/md-renderer/mdx-components/iframe.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
type IFrameProps = {
|
||||
title: string;
|
||||
src: string;
|
||||
};
|
||||
|
||||
const AspectRatioBox = styled.div`
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
margin-bottom: 18px;
|
||||
|
||||
&:before {
|
||||
height: 0;
|
||||
content: "";
|
||||
display: block;
|
||||
padding-bottom: 50%;
|
||||
}
|
||||
|
||||
& > iframe {
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export default function IFrame(props: IFrameProps) {
|
||||
return (
|
||||
<AspectRatioBox>
|
||||
<iframe
|
||||
frameBorder={0}
|
||||
title={props.title}
|
||||
src={props.src}
|
||||
allow={'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture'}
|
||||
allowFullScreen
|
||||
/>
|
||||
</AspectRatioBox>
|
||||
);
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
import P from './p';
|
||||
import { Headings } from './heading';
|
||||
import { Pre } from './pre';
|
||||
import BlockQuote from './blockquote';
|
||||
import { Table } from './table';
|
||||
import { IFrame } from './iframe';
|
||||
import { Img } from './img';
|
||||
import EnrichedLink from './a';
|
||||
import { BadgeLink } from '../../link/badge-link';
|
||||
|
||||
const MdxComponents = {
|
||||
p: P,
|
||||
...Headings,
|
||||
pre: Pre,
|
||||
blockquote: BlockQuote,
|
||||
a: EnrichedLink,
|
||||
table: Table,
|
||||
iframe: IFrame,
|
||||
img: Img,
|
||||
BadgeLink: BadgeLink
|
||||
};
|
||||
|
||||
export default MdxComponents;
|
32
components/md-renderer/mdx-components/index.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Code } from '@chakra-ui/react';
|
||||
import { P } from './p';
|
||||
import Headings from './heading';
|
||||
import { Pre } from './pre';
|
||||
import BlockQuote from './blockquote';
|
||||
import { Table } from './table';
|
||||
import IFrame from './iframe';
|
||||
import { Img } from './img';
|
||||
import EnrichedLink from './a';
|
||||
import { BadgeLink } from './badge-link';
|
||||
import { Li, Ul } from './ul';
|
||||
import PremiumBlock from './premium-block';
|
||||
import { ResourceGroupTitle } from './resource-group-title';
|
||||
|
||||
const MdxComponents = {
|
||||
p: P,
|
||||
...Headings,
|
||||
pre: Pre,
|
||||
blockquote: BlockQuote,
|
||||
a: EnrichedLink,
|
||||
table: Table,
|
||||
iframe: IFrame,
|
||||
img: Img,
|
||||
code: Code,
|
||||
BadgeLink: BadgeLink,
|
||||
ResourceGroupTitle: ResourceGroupTitle,
|
||||
PremiumBlock: PremiumBlock,
|
||||
ul: Ul,
|
||||
li: Li
|
||||
};
|
||||
|
||||
export default MdxComponents;
|
@@ -1,20 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const P = styled.p`
|
||||
color: inherit;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 26px;
|
||||
margin: 0 0 12px;
|
||||
|
||||
img + em {
|
||||
text-align: center;
|
||||
color: #666666;
|
||||
font-style: normal;
|
||||
font-size: 14px;
|
||||
margin: 5px 0 10px;
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
export default P;
|
14
components/md-renderer/mdx-components/p.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import { Text } from '@chakra-ui/react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
type EnrichedTextType = {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const P = styled.p`
|
||||
line-height: 27px;
|
||||
font-size: 16px;
|
||||
color: black;
|
||||
margin-bottom: 18px;
|
||||
`;
|
19
components/md-renderer/mdx-components/premium-block.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { Box, Button, Heading, Text } from '@chakra-ui/react';
|
||||
import { LockIcon } from '@chakra-ui/icons';
|
||||
|
||||
type PremiumBlockProps = {
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export default function PremiumBlock(props: PremiumBlockProps) {
|
||||
return (
|
||||
<Box p='40px' textAlign='center' rounded='5px' mb='18px' bg='gray.50' borderWidth={1}>
|
||||
<LockIcon color='gray.300' height='45px' w='45px' mb='18px' />
|
||||
<Heading as='h3' fontSize='30px' mb='10px'>{props.title}</Heading>
|
||||
<Text mb='18px'>{props.description}</Text>
|
||||
<Button colorScheme='green'>Become a Member</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import { Heading } from '@chakra-ui/react';
|
||||
|
||||
type ResourceGroupTitleProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export function ResourceGroupTitle(props: ResourceGroupTitleProps) {
|
||||
const { children } = props;
|
||||
|
||||
return <Heading mt='20px' color='gray.800' fontSize='14px' pb='5px' borderBottomWidth={1} textTransform='uppercase' as="h2" mb={'10px'}>{children}</Heading>;
|
||||
}
|
@@ -5,7 +5,7 @@ export const Table = styled.table`
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
margin: 20px 0;
|
||||
|
||||
|
||||
th {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
@@ -16,10 +16,10 @@ export const Table = styled.table`
|
||||
vertical-align: middle;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
|
||||
td {
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #EAEAEA;
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
16
components/md-renderer/mdx-components/ul.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import { UnorderedList } from '@chakra-ui/react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Ul = styled.ul`
|
||||
margin-left: 40px;
|
||||
margin-bottom: 18px;
|
||||
|
||||
ul {
|
||||
margin-top: 18px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Li = styled.li`
|
||||
margin-bottom: 7px;
|
||||
`;
|
46
components/opensource-banner.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Box, Container, Heading, Link, Text } from '@chakra-ui/react';
|
||||
|
||||
export function OpensourceBanner() {
|
||||
return (
|
||||
<Box bg='white' borderTopWidth={1} pt={['45px', '45px', '70px']} pb={['60px', '60px', '90px']} textAlign='center'>
|
||||
<Container maxW='container.md'>
|
||||
<Heading fontSize={['25px', '25px', '35px']} mb={['10px', '10px', '20px']}>Open Source</Heading>
|
||||
<Text lineHeight='26px' fontSize={['15px', '15px', '16px']} mb='20px'>The project is OpenSource,
|
||||
<Link
|
||||
_hover={{ textDecoration: 'none' }}
|
||||
href='https://github.com/search?o=desc&q=stars%3A%3E100000&s=stars&type=Repositories'
|
||||
target='_blank'
|
||||
borderBottomWidth={1}
|
||||
fontWeight={600}
|
||||
>6th most starred project on GitHub</Link> and is visited by hundreds of thousands of
|
||||
developers every month.</Text>
|
||||
<iframe
|
||||
src='https://ghbtns.com/github-btn.html?user=kamranahmedse&repo=developer-roadmap&type=star&count=true&size=large'
|
||||
frameBorder='0'
|
||||
scrolling='0'
|
||||
width='170'
|
||||
height='30'
|
||||
style={{ margin: 'auto', marginBottom: '30px' }}
|
||||
title='GitHub'
|
||||
/>
|
||||
|
||||
<Text lineHeight={['25px', '25px', '26px']} fontSize={['15px', '15px', '16px']} mb='15px'>A considerable amount of my time is spent doing unpaid
|
||||
community work on things that I hope will help humanity in some way. Your sponsorship helps me continue to
|
||||
produce more open-source and free educational material consumed by hundreds of thousands of developers every
|
||||
month.</Text>
|
||||
|
||||
<Box>
|
||||
<iframe
|
||||
src='https://ghbtns.com/github-btn.html?user=kamranahmedse&type=sponsor&size=large'
|
||||
frameBorder='0'
|
||||
scrolling='0'
|
||||
width='260'
|
||||
height='30'
|
||||
title='GitHub'
|
||||
style={{ margin: 'auto' }}
|
||||
/>
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
@@ -1,41 +0,0 @@
|
||||
export const CustomAd = () => {
|
||||
return (
|
||||
<div id="carbonads">
|
||||
<span>
|
||||
<span className="carbon-wrap">
|
||||
<a
|
||||
href="https://freemote.com/strategy?sl=roadmap"
|
||||
className="carbon-img"
|
||||
target="_blank"
|
||||
rel="noopener sponsored"
|
||||
>
|
||||
<img
|
||||
src="/fm-img.png"
|
||||
alt="FM Logo"
|
||||
border="0"
|
||||
height="100"
|
||||
width="130"
|
||||
style={{ maxWidth: "130px" }}
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://freemote.com/strategy?sl=roadmap"
|
||||
className="carbon-text"
|
||||
target="_blank"
|
||||
rel="noopener sponsored"
|
||||
>
|
||||
He Went from ZERO TO $74,000 as a Full Time Developer in 7 Weeks
|
||||
</a>
|
||||
</span>
|
||||
<a
|
||||
href="https://github.com/sponsors/kamranahmedse"
|
||||
className="carbon-poweredby"
|
||||
target="_blank"
|
||||
rel="noopener sponsored"
|
||||
>
|
||||
Sponsored by
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
@@ -1,71 +0,0 @@
|
||||
import siteConfig from 'content/site';
|
||||
import { FooterWrap } from './style.js';
|
||||
import './carbon.scss';
|
||||
import { CustomAd } from "./custom-ad";
|
||||
|
||||
const PageFooter = () => (
|
||||
<FooterWrap className="border-top">
|
||||
<div className="container">
|
||||
<div className="foot-cols-wrap row">
|
||||
<div className="site-meta-wrap col-12 col-sm-12 col-lg col-xl col-md-12">
|
||||
<div className="site-meta">
|
||||
<div className="brand-detail">
|
||||
<a href="/" className='brand'><img src="/brand.png" alt="" /> roadmap.sh</a>
|
||||
<span className="preposition">by</span>
|
||||
<a href="https://twitter.com/kamranahmedse" target="_blank" className='follow-author'>@kamranahmedse</a>
|
||||
</div>
|
||||
<div className="brand-explanation">
|
||||
<p>Community created roadmaps, articles, resources and journeys to help you choose your path and grow in your career.</p>
|
||||
</div>
|
||||
<p className='meta-links'>
|
||||
© roadmap.sh ·
|
||||
<a href="/about">FAQ</a> ·
|
||||
<a href="/terms">Terms</a> ·
|
||||
<a href="/privacy">Privacy</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="site-contribute foot-col col-12 col-sm-4 col-lg-2">
|
||||
<ul>
|
||||
<li className='foot-header'>Contribute</li>
|
||||
<li><a href='https://gum.co/roadmap-sh' target="_blank">Sponsor us</a></li>
|
||||
<li><a href={ siteConfig.url.addGuide } target="_blank">Write a Guide</a></li>
|
||||
<li><a href={ siteConfig.url.addRoadmap } target="_blank">Submit a Roadmap</a></li>
|
||||
<li><a href='/about'>About this Site</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="site-learn foot-col col-12 col-sm-4 col-lg-2">
|
||||
<ul>
|
||||
<li className="foot-header">Learn</li>
|
||||
<li><a href="/guides">Read Guides</a></li>
|
||||
<li><a href="/watch">Watch Videos</a></li>
|
||||
<li><a href="/podcasts">Podcasts</a></li>
|
||||
<li><a href="https://www.youtube.com/channel/UCA0H2KIWgWTwpTFjSxp0now?sub_confirmation=1" target="_blank">YouTube</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="site-learn foot-col col-12 col-sm-4 col-lg-2">
|
||||
<ul>
|
||||
<li className="foot-header">Most Visited</li>
|
||||
<li><a href="/frontend">Frontend Roadmap</a></li>
|
||||
<li><a href="/backend">Backend Roadmap</a></li>
|
||||
<li><a href="/devops">DevOps Roadmap</a></li>
|
||||
<li><a href="/roadmaps">Upcoming</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CustomAd />
|
||||
|
||||
{/* Do not show on local */}
|
||||
{ process.env.GA_SECRET && false && (
|
||||
<>
|
||||
<script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CE7DLK3Y&placement=roadmapsh" id="_carbonads_js"></script>
|
||||
{/*<div id="codefund"></div>*/}
|
||||
{/*<script src="https://app.codefund.io/properties/681/funder.js" async></script>*/}
|
||||
</>
|
||||
) }
|
||||
</FooterWrap>
|
||||
);
|
||||
|
||||
export default PageFooter;
|
@@ -1,94 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const FooterWrap = styled.div`
|
||||
padding: 65px 0;
|
||||
|
||||
.site-meta {
|
||||
margin-bottom: 30px;
|
||||
width: 350px;
|
||||
|
||||
.brand-detail {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
|
||||
img {
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.preposition {
|
||||
margin: 0 7px;
|
||||
}
|
||||
|
||||
.follow-author {
|
||||
background-color: #1e99e6;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
padding: 0 6px;
|
||||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
background: #43aaea;
|
||||
}
|
||||
}
|
||||
|
||||
.brand-explanation {
|
||||
color: #999;
|
||||
|
||||
p {
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.meta-links {
|
||||
color: #a3a3a3;
|
||||
font-size: 15px;
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.foot-col {
|
||||
margin-bottom: 20px;
|
||||
a {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.foot-header {
|
||||
font-weight: 500;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
margin-bottom: 2px;
|
||||
font-size: 15px;
|
||||
list-style: none;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
38
components/page-header.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Badge, Box, Container, Heading, Link, Text } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import siteConfig from '../content/site.json';
|
||||
|
||||
type PageHeaderProps = {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
children?: React.ReactNode;
|
||||
beforeTitle?: React.ReactNode;
|
||||
};
|
||||
|
||||
export function PageHeader(props: PageHeaderProps) {
|
||||
const { title, subtitle, children, beforeTitle = null } = props;
|
||||
|
||||
return (
|
||||
<Box pt={['25px', '20px', '45px']} pb={['20px', '15px', '30px']} borderBottomWidth={1} mb='30px'>
|
||||
<Container maxW='container.md' position='relative'>
|
||||
{beforeTitle}
|
||||
<Heading
|
||||
as='h1'
|
||||
color='black'
|
||||
fontSize={['28px', '33px', '40px']}
|
||||
fontWeight={700}
|
||||
mb={['2px', '2px', '5px']}
|
||||
>
|
||||
{title}
|
||||
</Heading>
|
||||
<Text fontSize={['13px', '14px', '15px']}>{subtitle}</Text>
|
||||
</Container>
|
||||
|
||||
{children && (
|
||||
<Container maxW='container.md'>
|
||||
{children}
|
||||
</Container>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
import { HeaderWrap, Subtitle, Title } from './style';
|
||||
|
||||
const PageHeader = ({
|
||||
title,
|
||||
subtitle,
|
||||
children,
|
||||
}) => (
|
||||
<HeaderWrap>
|
||||
<Title>{ title }</Title>
|
||||
<Subtitle dangerouslySetInnerHTML={{ __html: subtitle }} />
|
||||
|
||||
{ children }
|
||||
</HeaderWrap>
|
||||
);
|
||||
|
||||
export default PageHeader;
|
@@ -1,22 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const HeaderWrap = styled.div`
|
||||
text-align: center;
|
||||
padding: 45px 30px;
|
||||
`;
|
||||
|
||||
export const Title = styled.h1`
|
||||
font-size: 48px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
`;
|
||||
|
||||
export const Subtitle = styled.p`
|
||||
font-size: 16px;
|
||||
color: #444;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
font-weight: 700;
|
||||
}
|
||||
`;
|
@@ -1,13 +0,0 @@
|
||||
import { HeaderWrap, Subtitle, Title, Logo } from './style';
|
||||
|
||||
const PageLogoHeader = ({ title, subtitle, children, }) => (
|
||||
<HeaderWrap>
|
||||
<Logo src='/brand.png' alt='' />
|
||||
<Title>{ title }</Title>
|
||||
<Subtitle dangerouslySetInnerHTML={{ __html: subtitle }} />
|
||||
|
||||
{ children }
|
||||
</HeaderWrap>
|
||||
);
|
||||
|
||||
export default PageLogoHeader;
|
@@ -1,30 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const HeaderWrap = styled.div`
|
||||
text-align: center;
|
||||
padding: 45px 30px;
|
||||
`;
|
||||
|
||||
export const Title = styled.h1`
|
||||
font-size: 40px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
`;
|
||||
|
||||
export const Subtitle = styled.p`
|
||||
font-size: 16px;
|
||||
color: #444;
|
||||
margin-bottom: 0;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
font-weight: 700;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
export const Logo = styled.img`
|
||||
width: 75px;
|
||||
height: 75px;
|
||||
margin-bottom: 26px;
|
||||
`;
|
16
components/page-wrapper.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
|
||||
type PageWrapperProps = {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function PageWrapper(props: PageWrapperProps) {
|
||||
const { children } = props;
|
||||
|
||||
return (
|
||||
<Box bgColor='brand.bg' bgImage='url(/bg.jpg)' bgRepeat='no-repeat' bgSize='100%' w='100%' minH='100vh'>
|
||||
{ children }
|
||||
</Box>
|
||||
);
|
||||
}
|
@@ -1,62 +0,0 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faArrowLeft, faClock, faEnvelope, faHandshake } from '@fortawesome/free-solid-svg-icons';
|
||||
import { BadgeLink, BadgesList, DarkBadge, PrimaryBadge, SecondaryBadge } from 'components/badges';
|
||||
import siteConfig from "content/site";
|
||||
import { Description, Header, Title, MenuItemLink, MenuItems } from './style';
|
||||
import Link from 'next/link';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const RoadmapHeader = ({ roadmap, page = 'landscape' }) => (
|
||||
<Header>
|
||||
<Title>{ roadmap.title }</Title>
|
||||
<Description>{ roadmap.description }</Description>
|
||||
<BadgesList className="mt-4">
|
||||
<BadgeLink href="/roadmaps">
|
||||
<DarkBadge>
|
||||
<FontAwesomeIcon className='d-none d-md-block' icon={ faArrowLeft } />
|
||||
Other Roadmaps
|
||||
</DarkBadge>
|
||||
</BadgeLink>
|
||||
{ roadmap.upcoming && (
|
||||
<BadgeLink href="/signup">
|
||||
<SecondaryBadge>
|
||||
<FontAwesomeIcon className='d-none d-md-block' icon={ faClock } />
|
||||
Upcoming Roadmap
|
||||
</SecondaryBadge>
|
||||
</BadgeLink>
|
||||
) }
|
||||
{ !roadmap.upcoming && (
|
||||
<BadgeLink href={ `${siteConfig.url.issue}?title=[${roadmap.title}] - Title Here` } target="_blank" className='d-none d-md-block' >
|
||||
<SecondaryBadge>
|
||||
<FontAwesomeIcon icon={ faHandshake } />
|
||||
Suggest Changes
|
||||
</SecondaryBadge>
|
||||
</BadgeLink>
|
||||
) }
|
||||
|
||||
<BadgeLink href="/signup">
|
||||
<PrimaryBadge>
|
||||
<FontAwesomeIcon className='d-none d-md-block' icon={ faEnvelope } />
|
||||
Send me Updates
|
||||
</PrimaryBadge>
|
||||
</BadgeLink>
|
||||
</BadgesList>
|
||||
|
||||
<MenuItems className="border-bottom">
|
||||
<div className={ classNames({ 'd-none': roadmap.title.toLowerCase() !== 'frontend developer' })}>
|
||||
<Link href={ `${roadmap.url}` } passHref>
|
||||
<MenuItemLink className={ classNames({ active: page === 'landscape', }) }>Landscape</MenuItemLink>
|
||||
</Link>
|
||||
<Link href={ `${roadmap.url}/resources` } passHref>
|
||||
<MenuItemLink className={ classNames({ active: page === 'resources', }) }>Resources</MenuItemLink>
|
||||
</Link>
|
||||
{/*<Link href={ `${roadmap.url}/resources` } passHref>*/}
|
||||
{/* <MenuItemLink className={ classNames({ active: false, }) }>Project Ideas</MenuItemLink>*/}
|
||||
{/*</Link>*/}
|
||||
</div>
|
||||
</MenuItems>
|
||||
|
||||
</Header>
|
||||
);
|
||||
|
||||
export default RoadmapHeader;
|
@@ -1,50 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Header = styled.div`
|
||||
text-align: center;
|
||||
padding: 45px 30px 10px;
|
||||
`;
|
||||
|
||||
export const Title = styled.h1`
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
font-size: 48px;
|
||||
`;
|
||||
|
||||
export const Description = styled.p`
|
||||
font-size: 16px;
|
||||
color: #444444;
|
||||
`;
|
||||
|
||||
export const MenuItems = styled.div`
|
||||
margin: 35px 0 15px;
|
||||
`;
|
||||
|
||||
export const MenuItemLink = styled.a`
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding: 5px 10px 8px;
|
||||
text-decoration: none;
|
||||
color: rgb(102, 102, 102);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
&.active, &.active:hover {
|
||||
color: #2d2d2d;
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
display: block;
|
||||
height: 0;
|
||||
left: 9px;
|
||||
right: 9px;
|
||||
bottom: -1px;
|
||||
border-bottom: 2px solid currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
color: #111111;
|
||||
}
|
||||
`;
|
@@ -1,35 +0,0 @@
|
||||
import { Summary, SummaryContainer, UpcomingContainer } from './style';
|
||||
import GuideBody from 'components/guide-body';
|
||||
import RoadmapHeader from 'components/roadmap-header';
|
||||
import SharePage from 'components/share-page';
|
||||
import MdRenderer from 'components/md-renderer';
|
||||
|
||||
const RoadmapResources = ({ roadmap }) => {
|
||||
if (roadmap.upcoming) {
|
||||
return (
|
||||
<>
|
||||
<RoadmapHeader roadmap={ roadmap } />
|
||||
<UpcomingContainer>
|
||||
<GuideBody guide={{ fileName: 'upcoming' }} />
|
||||
</UpcomingContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const filePath = roadmap.resources.replace(/^\//, '');
|
||||
const ResourcesContent = require(`../../content/${filePath}`).default;
|
||||
|
||||
return (
|
||||
<SummaryContainer>
|
||||
<RoadmapHeader roadmap={ roadmap } page='resources' />
|
||||
<Summary className="container">
|
||||
<MdRenderer>
|
||||
<ResourcesContent />
|
||||
</MdRenderer>
|
||||
<SharePage title={ roadmap.description } url={ roadmap.url } />
|
||||
</Summary>
|
||||
</SummaryContainer>
|
||||
)
|
||||
};
|
||||
|
||||
export default RoadmapResources;
|
@@ -1,21 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SummaryContainer = styled.div``;
|
||||
|
||||
export const UpcomingContainer = styled.div`
|
||||
text-align: center;
|
||||
padding: 40px 0 50px;
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Summary = styled.div`
|
||||
margin-top: 35px;
|
||||
min-height: 400px;
|
||||
max-width: 1000px;
|
||||
display: block;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
`;
|
@@ -1,36 +0,0 @@
|
||||
import { Summary, SummaryContainer, UpcomingContainer } from './style';
|
||||
import classNames from 'classnames';
|
||||
import GuideBody from 'components/guide-body';
|
||||
import RoadmapHeader from 'components/roadmap-header';
|
||||
import SharePage from 'components/share-page';
|
||||
import MdRenderer from 'components/md-renderer';
|
||||
|
||||
const RoadmapSummary = ({ roadmap }) => {
|
||||
if (roadmap.upcoming) {
|
||||
return (
|
||||
<>
|
||||
<RoadmapHeader roadmap={ roadmap } />
|
||||
<UpcomingContainer>
|
||||
<GuideBody guide={{ fileName: 'upcoming' }} />
|
||||
</UpcomingContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const filePath = roadmap.path.replace(/^\//, '');
|
||||
const RoadmapContent = require(`../../content/${filePath}`).default;
|
||||
|
||||
return (
|
||||
<SummaryContainer>
|
||||
<RoadmapHeader roadmap={ roadmap } />
|
||||
<Summary className={classNames("container", { "container-small": roadmap.isTextHeavy })}>
|
||||
<MdRenderer>
|
||||
<RoadmapContent />
|
||||
</MdRenderer>
|
||||
<SharePage title={ roadmap.description } url={ roadmap.url } />
|
||||
</Summary>
|
||||
</SummaryContainer>
|
||||
)
|
||||
};
|
||||
|
||||
export default RoadmapSummary;
|
@@ -1,21 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SummaryContainer = styled.div``;
|
||||
|
||||
export const UpcomingContainer = styled.div`
|
||||
text-align: center;
|
||||
padding: 40px 0 50px;
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Summary = styled.div`
|
||||
margin-top: 35px;
|
||||
min-height: 400px;
|
||||
max-width: 1000px;
|
||||
display: block;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
`;
|
115
components/roadmap/content-drawer.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import { Box, Button, Flex, Text } from '@chakra-ui/react';
|
||||
import { RemoveScroll } from 'react-remove-scroll';
|
||||
import { RoadmapType } from '../../lib/roadmap';
|
||||
import RoadmapGroup from '../../pages/[roadmap]/[group]';
|
||||
import { CheckIcon, CloseIcon, RepeatIcon } from '@chakra-ui/icons';
|
||||
|
||||
type ContentDrawerProps = {
|
||||
roadmap: RoadmapType;
|
||||
groupId: string;
|
||||
onClose?: () => void;
|
||||
};
|
||||
|
||||
export function ContentDrawer(props: ContentDrawerProps) {
|
||||
const { roadmap, groupId, onClose = () => null } = props;
|
||||
if (!groupId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isDone = localStorage.getItem(groupId) === 'done';
|
||||
|
||||
return (
|
||||
<Box zIndex={99999} pos="relative">
|
||||
<Box
|
||||
onClick={onClose}
|
||||
pos="fixed"
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
bg="black"
|
||||
opacity={0.4}
|
||||
/>
|
||||
<RemoveScroll allowPinchZoom>
|
||||
<Box
|
||||
p="0px 30px 30px"
|
||||
position="fixed"
|
||||
w={['100%', '60%', '40%']}
|
||||
bg="white"
|
||||
top={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
borderLeftWidth={'1px'}
|
||||
overflowY="scroll"
|
||||
>
|
||||
<Flex
|
||||
mt="20px"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
zIndex={1}
|
||||
>
|
||||
{!isDone && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
localStorage.setItem(groupId, 'done');
|
||||
document
|
||||
.querySelectorAll(`[data-group-id*="-${groupId}"]`)
|
||||
.forEach((item) => item?.classList?.add('done'));
|
||||
onClose();
|
||||
}}
|
||||
colorScheme="green"
|
||||
leftIcon={<CheckIcon />}
|
||||
size="xs"
|
||||
iconSpacing={0}
|
||||
>
|
||||
<Text
|
||||
as="span"
|
||||
d={['block', 'none', 'none', 'block']}
|
||||
ml="10px"
|
||||
>
|
||||
Mark as Done
|
||||
</Text>
|
||||
</Button>
|
||||
)}
|
||||
{isDone && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
localStorage.removeItem(groupId);
|
||||
document
|
||||
.querySelectorAll(`[data-group-id*="-${groupId}"]`)
|
||||
.forEach((item) => item?.classList?.remove('done'));
|
||||
onClose();
|
||||
}}
|
||||
colorScheme="red"
|
||||
leftIcon={<RepeatIcon />}
|
||||
size="xs"
|
||||
iconSpacing={0}
|
||||
>
|
||||
<Text
|
||||
as="span"
|
||||
d={['block', 'none', 'none', 'block']}
|
||||
ml="10px"
|
||||
>
|
||||
Mark as Pending
|
||||
</Text>
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={onClose}
|
||||
colorScheme="yellow"
|
||||
ml="5px"
|
||||
leftIcon={<CloseIcon width="8px" />}
|
||||
iconSpacing={0}
|
||||
size="xs"
|
||||
>
|
||||
<Text as="span" d={['none', 'none', 'none', 'block']} ml="10px">
|
||||
Close
|
||||
</Text>
|
||||
</Button>
|
||||
</Flex>
|
||||
<RoadmapGroup isOutlet roadmap={roadmap} group={groupId} />
|
||||
</Box>
|
||||
</RemoveScroll>
|
||||
</Box>
|
||||
);
|
||||
}
|
39
components/roadmap/edit-content-page-link.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { Box, Button, Divider, Link, Text } from '@chakra-ui/react';
|
||||
|
||||
type EditContentPageLinkProps = {
|
||||
href: string;
|
||||
};
|
||||
|
||||
export function EditContentPageLink(props: EditContentPageLinkProps) {
|
||||
const { href } = props;
|
||||
|
||||
return (
|
||||
<Box my='30px'>
|
||||
<Divider mb="15px" orientation="horizontal" />
|
||||
<Text
|
||||
lineHeight="23px"
|
||||
fontWeight={500}
|
||||
fontSize="14px"
|
||||
color="gray.500"
|
||||
mb="10px"
|
||||
>
|
||||
This page is a work in progress. Help us by writing a small
|
||||
introduction to the topic and suggesting a few links to read more
|
||||
about this topic.
|
||||
</Text>
|
||||
<Button
|
||||
size="sm"
|
||||
py="20px"
|
||||
as={Link}
|
||||
href={href}
|
||||
target="_blank"
|
||||
isFullWidth
|
||||
colorScheme={'gray'}
|
||||
_hover={{ textDecoration: 'none', bg: 'gray.200' }}
|
||||
>
|
||||
Edit this Page
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
114
components/roadmap/home-roadmap-item.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import { Box, Flex, Heading, Link, Text, Tooltip } from '@chakra-ui/react';
|
||||
import { InfoIcon } from '@chakra-ui/icons';
|
||||
|
||||
type RoadmapGridItemProps = {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
isCommunity?: boolean;
|
||||
isUpcoming?: boolean;
|
||||
colorIndex?: number;
|
||||
url: string;
|
||||
};
|
||||
|
||||
const bgColorList = [
|
||||
'red.100',
|
||||
'yellow.100',
|
||||
'green.200',
|
||||
'teal.200',
|
||||
'blue.200',
|
||||
'red.200',
|
||||
'gray.200',
|
||||
'teal.200',
|
||||
'yellow.100',
|
||||
'green.200',
|
||||
'red.200',
|
||||
];
|
||||
|
||||
export function HomeRoadmapItem(props: RoadmapGridItemProps) {
|
||||
const {
|
||||
title,
|
||||
subtitle,
|
||||
isCommunity,
|
||||
colorIndex = 0,
|
||||
url,
|
||||
isUpcoming,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Box
|
||||
as={Link}
|
||||
href={url}
|
||||
_hover={{
|
||||
textDecoration: 'none',
|
||||
bg: 'rgba(255,255,255,.10)',
|
||||
}}
|
||||
sx={{
|
||||
// On mobile devices, don't change the scale
|
||||
'@media (hover: none)': {
|
||||
'&:hover': {
|
||||
bg: 'rgba(255,255,255,.05)',
|
||||
},
|
||||
},
|
||||
}}
|
||||
flex={1}
|
||||
shadow="2xl"
|
||||
className={'home-roadmap-item'}
|
||||
bg={'rgba(255,255,255,.05)'}
|
||||
color="white"
|
||||
p="15px"
|
||||
rounded="10px"
|
||||
pos="relative"
|
||||
>
|
||||
{isCommunity && (
|
||||
<Tooltip label={'Community contribution'} hasArrow placement="top">
|
||||
<InfoIcon opacity={0.5} position="absolute" top="10px" right="10px" />
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<Heading
|
||||
fontSize={['17px', '17px', '22px']}
|
||||
color={bgColorList[colorIndex]}
|
||||
mb="5px"
|
||||
>
|
||||
{title}
|
||||
</Heading>
|
||||
<Text color="gray.200" fontSize={['13px']}>
|
||||
{subtitle}
|
||||
</Text>
|
||||
|
||||
{isUpcoming && (
|
||||
<Flex
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
pos="absolute"
|
||||
left={0}
|
||||
right={0}
|
||||
top={0}
|
||||
bottom={0}
|
||||
rounded="10px"
|
||||
>
|
||||
<Text
|
||||
color="white"
|
||||
bg="gray.600"
|
||||
zIndex={1}
|
||||
fontWeight={600}
|
||||
p={'5px 10px'}
|
||||
rounded="10px"
|
||||
>
|
||||
Upcoming
|
||||
</Text>
|
||||
<Box
|
||||
bg={'black'}
|
||||
pos="absolute"
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
rounded={'10px'}
|
||||
opacity={0.5}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
50
components/roadmap/new-alert-banner.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Badge, Link, Text } from '@chakra-ui/react';
|
||||
import siteConfig from '../../content/site.json';
|
||||
import { event } from '../../lib/gtag';
|
||||
import React from 'react';
|
||||
|
||||
export function NewAlertBanner() {
|
||||
return (
|
||||
<Text
|
||||
_hover={{
|
||||
textDecoration: 'none',
|
||||
color: 'blue.700',
|
||||
'& .new-badge': { bg: 'blue.700' },
|
||||
}}
|
||||
as={Link}
|
||||
href={siteConfig.url.youtube}
|
||||
d="block"
|
||||
target="_blank"
|
||||
color="red.700"
|
||||
fontSize="sm"
|
||||
mb="10px"
|
||||
fontWeight={500}
|
||||
onClick={() =>
|
||||
event({
|
||||
category: 'Subscription',
|
||||
action: 'Clicked the YouTube banner',
|
||||
label: 'YouTube Alert on Roadmap',
|
||||
})
|
||||
}
|
||||
>
|
||||
<Badge
|
||||
transition={'all 300ms'}
|
||||
className="new-badge"
|
||||
mr="7px"
|
||||
colorScheme="red"
|
||||
variant="solid"
|
||||
>
|
||||
New
|
||||
</Badge>
|
||||
<Text textDecoration="underline" as="span" d={['none', 'inline']}>
|
||||
Roadmap topics to be covered on our YouTube Channel
|
||||
</Text>
|
||||
<Text textDecoration="underline" as="span" d={['inline', 'none']}>
|
||||
Topic videos being made on YouTube
|
||||
</Text>
|
||||
<Text as="span" ml="5px">
|
||||
»
|
||||
</Text>
|
||||
</Text>
|
||||
);
|
||||
}
|
26
components/roadmap/roadmap-error.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { RoadmapType } from '../../lib/roadmap';
|
||||
import { Container, Heading, Link, Text } from '@chakra-ui/react';
|
||||
import siteConfig from '../../content/site.json';
|
||||
|
||||
type RoadmapProps = {
|
||||
roadmap: RoadmapType;
|
||||
};
|
||||
|
||||
export function RoadmapError(props: RoadmapProps) {
|
||||
const { roadmap } = props;
|
||||
|
||||
return (
|
||||
<Container
|
||||
bg={'red.600'}
|
||||
maxW={'container.md'}
|
||||
position="relative"
|
||||
mt="50px"
|
||||
p='20px'
|
||||
rounded='5px'
|
||||
color='white'
|
||||
>
|
||||
<Heading mb='4px' size='md'>Oops! There's an error</Heading>
|
||||
<Text>Try refreshing or <Link target='_blank' fontWeight={700} textDecoration={'underline'} fontSize='14px' href={siteConfig.url.issue}>report a bug</Link> and use the <Link fontWeight={700} textDecoration={'underline'} href={`/roadmaps/${roadmap.id}.png`}>non-interactive version</Link></Text>
|
||||
</Container>
|
||||
);
|
||||
}
|
89
components/roadmap/roadmap-grid-item.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { Badge, Box, Flex, Heading, Link, Text, Tooltip } from '@chakra-ui/react';
|
||||
import { InfoIcon } from '@chakra-ui/icons';
|
||||
|
||||
type RoadmapGridItemProps = {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
href: string;
|
||||
isCommunity?: boolean;
|
||||
isUpcoming?: boolean;
|
||||
colorIndex?: number;
|
||||
};
|
||||
|
||||
const bgColorList = [
|
||||
'gray.900',
|
||||
'purple.900',
|
||||
'blue.900',
|
||||
'red.900',
|
||||
'green.900',
|
||||
'teal.900',
|
||||
'yellow.900',
|
||||
'cyan.900',
|
||||
'pink.900',
|
||||
|
||||
'gray.800',
|
||||
'purple.800',
|
||||
'blue.800',
|
||||
'red.800',
|
||||
'green.800',
|
||||
'teal.800',
|
||||
'yellow.800',
|
||||
'cyan.800',
|
||||
'pink.800',
|
||||
|
||||
'gray.700',
|
||||
'purple.700',
|
||||
'blue.700',
|
||||
'red.700',
|
||||
'green.700',
|
||||
'teal.700',
|
||||
'yellow.700',
|
||||
'cyan.700',
|
||||
'pink.700',
|
||||
|
||||
'gray.600',
|
||||
'purple.600',
|
||||
'blue.600',
|
||||
'red.600',
|
||||
'green.600',
|
||||
'teal.600',
|
||||
'yellow.600',
|
||||
'cyan.600',
|
||||
'pink.600'
|
||||
];
|
||||
|
||||
export function RoadmapGridItem(props: RoadmapGridItemProps) {
|
||||
const { title, subtitle, isCommunity = false, isUpcoming = false, colorIndex = 0, href = '/' } = props;
|
||||
|
||||
return (
|
||||
<Box _hover={{ textDecoration: 'none', transform: 'scale(1.02)' }} as={Link} href={href} shadow='xl' p='20px'
|
||||
rounded='10px' bg={bgColorList[colorIndex] ?? bgColorList[0]} flex={1} pos='relative'>
|
||||
|
||||
{isCommunity && (
|
||||
<Tooltip label={'Community contribution'} hasArrow placement='top'>
|
||||
<InfoIcon opacity={0.5} color='gray.100' position='absolute' top='10px' right='10px' />
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<Heading color='white' mb={'6px'} fontSize='20px'>{title}</Heading>
|
||||
<Text color='gray.300' fontSize='14px'>{subtitle}</Text>
|
||||
|
||||
{isUpcoming && (
|
||||
<Flex
|
||||
alignItems='center'
|
||||
justifyContent='center'
|
||||
pos='absolute'
|
||||
left={0}
|
||||
right={0}
|
||||
top={0}
|
||||
bottom={0}
|
||||
rounded='10px'
|
||||
>
|
||||
<Text color='white' bg='yellow.900' zIndex={1} fontWeight={600} p={'5px 10px'}
|
||||
rounded='10px'>Upcoming</Text>
|
||||
<Box bg={'black'} pos='absolute' top={0} left={0} right={0} bottom={0} rounded={'10px'} opacity={0.5} />
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
20
components/roadmap/roadmap-loader.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Container, Spinner } from '@chakra-ui/react';
|
||||
|
||||
export function RoadmapLoader() {
|
||||
return (
|
||||
<Container
|
||||
maxW={'container.md'}
|
||||
position="relative"
|
||||
mt="60px"
|
||||
textAlign="center"
|
||||
>
|
||||
<Spinner
|
||||
thickness="7px"
|
||||
speed="0.65s"
|
||||
emptyColor="gray.200"
|
||||
color="gray.500"
|
||||
size="xl"
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
113
components/roadmap/roadmap-page-header.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import { RoadmapType } from '../../lib/roadmap';
|
||||
import { NewAlertBanner } from './new-alert-banner';
|
||||
import {
|
||||
Badge,
|
||||
Box,
|
||||
Button,
|
||||
Container,
|
||||
Flex,
|
||||
Heading,
|
||||
Link,
|
||||
Stack,
|
||||
Text,
|
||||
} from '@chakra-ui/react';
|
||||
import { AtSignIcon, DownloadIcon } from '@chakra-ui/icons';
|
||||
import React from 'react';
|
||||
|
||||
type RoadmapPageHeaderType = {
|
||||
roadmap: RoadmapType;
|
||||
};
|
||||
|
||||
export function RoadmapPageHeader(props: RoadmapPageHeaderType) {
|
||||
const { roadmap } = props;
|
||||
|
||||
return (
|
||||
<Box
|
||||
pt={['25px', '20px', '45px']}
|
||||
pb={['20px', '15px', '30px']}
|
||||
borderBottomWidth={1}
|
||||
mb="30px"
|
||||
>
|
||||
<Container maxW="container.md" position="relative">
|
||||
<NewAlertBanner />
|
||||
<Heading
|
||||
as="h1"
|
||||
color="black"
|
||||
fontSize={['28px', '33px', '40px']}
|
||||
fontWeight={700}
|
||||
mb={['2px', '2px', '5px']}
|
||||
>
|
||||
{roadmap.title}
|
||||
</Heading>
|
||||
<Text fontSize={['13px', '14px', '15px']}>{roadmap.description}</Text>
|
||||
<Flex justifyContent="space-between" alignItems={'center'} mt="20px">
|
||||
<Stack isInline>
|
||||
<Button
|
||||
d={['flex', 'flex']}
|
||||
as={Link}
|
||||
href={'/roadmaps'}
|
||||
size="xs"
|
||||
py="14px"
|
||||
px="10px"
|
||||
colorScheme="teal"
|
||||
variant="solid"
|
||||
_hover={{ textDecoration: 'none' }}
|
||||
>
|
||||
←
|
||||
<Text as="span" d={['none', 'inline']} ml="5px">
|
||||
All Roadmaps
|
||||
</Text>
|
||||
</Button>
|
||||
|
||||
{roadmap.pdfUrl && (
|
||||
<Button
|
||||
as={Link}
|
||||
href={roadmap.pdfUrl}
|
||||
target="_blank"
|
||||
size="xs"
|
||||
py="14px"
|
||||
px="10px"
|
||||
leftIcon={<DownloadIcon />}
|
||||
colorScheme="yellow"
|
||||
variant="solid"
|
||||
_hover={{ textDecoration: 'none' }}
|
||||
>
|
||||
Download
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
as={Link}
|
||||
href={'/signup'}
|
||||
size="xs"
|
||||
py="14px"
|
||||
px="10px"
|
||||
variant="solid"
|
||||
colorScheme="yellow"
|
||||
leftIcon={<AtSignIcon />}
|
||||
_hover={{ textDecoration: 'none' }}
|
||||
>
|
||||
Subscribe
|
||||
</Button>
|
||||
</Stack>
|
||||
</Flex>
|
||||
{roadmap.id === 'frontend' && (
|
||||
<Text
|
||||
mt="30px"
|
||||
mb={['-37px', '-32px', '-47px']}
|
||||
fontWeight={500}
|
||||
fontSize="14px"
|
||||
bg="white"
|
||||
borderWidth={1}
|
||||
p="5px 7px"
|
||||
rounded="3px"
|
||||
>
|
||||
<Badge pos="relative" top={'-1px'} mr="6px" colorScheme="yellow">
|
||||
New
|
||||
</Badge>
|
||||
Resources are here, try clicking any nodes.
|
||||
</Text>
|
||||
)}
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
@@ -1,28 +0,0 @@
|
||||
import { Badge, BlockItem, ItemSubtitle, ItemTitle, ItemWrap } from './style';
|
||||
|
||||
const RowBlock = ({
|
||||
title,
|
||||
subtitle,
|
||||
url,
|
||||
badge,
|
||||
openExternal = false,
|
||||
disabled = false,
|
||||
children = null
|
||||
}) => (
|
||||
<ItemWrap className="col-md-6 col-lg-4 col-xl-4">
|
||||
<BlockItem href={ url } disabled={ disabled } target={openExternal ? '_blank' : '_self'}>
|
||||
{ !children && (
|
||||
<>
|
||||
<ItemTitle>
|
||||
{ title }
|
||||
{ badge && <Badge>{ badge }</Badge>}
|
||||
</ItemTitle>
|
||||
<ItemSubtitle>{ subtitle }</ItemSubtitle>
|
||||
</>
|
||||
) }
|
||||
{ children }
|
||||
</BlockItem>
|
||||
</ItemWrap>
|
||||
);
|
||||
|
||||
export default RowBlock;
|
@@ -1,62 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const ItemWrap = styled.div`
|
||||
padding: 0 10px 20px;
|
||||
`;
|
||||
|
||||
export const BlockItem = styled.a`
|
||||
min-height: 114px;
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 5px 10px;
|
||||
transition: box-shadow 0.2s ease 0s;
|
||||
align-items: stretch;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 1px;
|
||||
max-width: 100%;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
justify-content: center;
|
||||
text-decoration:none;
|
||||
opacity: ${ props => props.disabled ? 0.5 : 1 };
|
||||
|
||||
&:hover {
|
||||
text-decoration:none;
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 30px 60px;
|
||||
z-index: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ItemTitle = styled.h3`
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
color: #101010;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
`;
|
||||
|
||||
export const Badge = styled.span`
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
background: #8a8a8a;
|
||||
color: white;
|
||||
padding: 0 8px;
|
||||
border-radius: 4px;
|
||||
text-transform: uppercase;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 10px;
|
||||
height: 18px;
|
||||
`;
|
||||
|
||||
export const ItemSubtitle = styled.p`
|
||||
font-size: 14px;
|
||||
color: rgb(102, 102, 102);
|
||||
white-space: normal;
|
||||
line-height: 1.5;
|
||||
margin: 0;
|
||||
`;
|
@@ -1,21 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const ShareIcon = styled.a`
|
||||
display: block;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
|
||||
svg {
|
||||
height: 22px !important;
|
||||
width: 22px !important;
|
||||
color: #757575;
|
||||
transition: all 0.2s;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
&:hover svg {
|
||||
color: #000000
|
||||
}
|
||||
`;
|