mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2026-03-13 10:11:55 +08:00
Compare commits
46 Commits
roadmap/de
...
images-fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3b43e3068 | ||
|
|
c95272e213 | ||
|
|
689792665b | ||
|
|
ae94ddfc04 | ||
|
|
b47f712dec | ||
|
|
48b0059ade | ||
|
|
2f07d5edf0 | ||
|
|
1dd9a53a32 | ||
|
|
540e13271e | ||
|
|
12f617bd0e | ||
|
|
82a7c205eb | ||
|
|
76d1ca1333 | ||
|
|
40357f7956 | ||
|
|
581f4a76a4 | ||
|
|
ef1a3031c4 | ||
|
|
3774f3c5ec | ||
|
|
b11da48f41 | ||
|
|
5edda5654c | ||
|
|
505077a545 | ||
|
|
9f4967929f | ||
|
|
27cb89494f | ||
|
|
ec556915e4 | ||
|
|
c61e44119d | ||
|
|
6f46d723bc | ||
|
|
ee6e3e4029 | ||
|
|
6e9fe97e5c | ||
|
|
13af03c930 | ||
|
|
78692ff13f | ||
|
|
54d7388b09 | ||
|
|
b609c43055 | ||
|
|
d83fe1279b | ||
|
|
fb3cb85c14 | ||
|
|
82dbca95fb | ||
|
|
7e702ee385 | ||
|
|
08fbb730ab | ||
|
|
cd80338fa6 | ||
|
|
fa33d0c339 | ||
|
|
8ec9a6e675 | ||
|
|
16853df928 | ||
|
|
c15d139d54 | ||
|
|
4e5cc5bd35 | ||
|
|
a36bca2f42 | ||
|
|
10b688049d | ||
|
|
0db92f6418 | ||
|
|
dccaa66ed4 | ||
|
|
3deee4dfc3 |
@@ -1,4 +1,4 @@
|
||||
name: Deployment to GH Pages
|
||||
name: App Deployment
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
11028
package-lock.json
generated
11028
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
38
package.json
38
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "roadmap.sh",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "astro dev --port 3000",
|
||||
@@ -22,23 +22,23 @@
|
||||
"test:e2e": "playwright test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/react": "^3.0.3",
|
||||
"@astrojs/sitemap": "^3.0.2",
|
||||
"@astrojs/react": "^3.0.4",
|
||||
"@astrojs/sitemap": "^3.0.3",
|
||||
"@astrojs/tailwind": "^5.0.2",
|
||||
"@fingerprintjs/fingerprintjs": "^4.1.0",
|
||||
"@nanostores/react": "^0.7.1",
|
||||
"@types/react": "^18.2.31",
|
||||
"@types/react-dom": "^18.2.14",
|
||||
"astro": "^3.3.3",
|
||||
"astro-compress": "^2.1.5",
|
||||
"@types/react": "^18.2.37",
|
||||
"@types/react-dom": "^18.2.15",
|
||||
"astro": "^3.5.0",
|
||||
"astro-compress": "^2.2.3",
|
||||
"clsx": "^2.0.0",
|
||||
"dracula-prism": "^2.1.13",
|
||||
"jose": "^4.15.4",
|
||||
"jose": "^5.1.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"lucide-react": "^0.288.0",
|
||||
"nanoid": "^5.0.2",
|
||||
"lucide-react": "^0.292.0",
|
||||
"nanoid": "^5.0.3",
|
||||
"nanostores": "^0.9.4",
|
||||
"node-html-parser": "^6.1.10",
|
||||
"node-html-parser": "^6.1.11",
|
||||
"npm-check-updates": "^16.14.6",
|
||||
"prismjs": "^1.29.0",
|
||||
"react": "^18.2.0",
|
||||
@@ -48,22 +48,22 @@
|
||||
"rehype-external-links": "^3.0.0",
|
||||
"roadmap-renderer": "^1.0.6",
|
||||
"slugify": "^1.6.6",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"zustand": "^4.4.4"
|
||||
"tailwind-merge": "^2.0.0",
|
||||
"tailwindcss": "^3.3.5",
|
||||
"zustand": "^4.4.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.39.0",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"@types/js-cookie": "^3.0.5",
|
||||
"@types/prismjs": "^1.26.2",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/prismjs": "^1.26.3",
|
||||
"csv-parser": "^3.0.0",
|
||||
"gh-pages": "^6.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"markdown-it": "^13.0.2",
|
||||
"openai": "^4.13.0",
|
||||
"openai": "^4.17.1",
|
||||
"prettier": "^3.0.3",
|
||||
"prettier-plugin-astro": "^0.12.0",
|
||||
"prettier-plugin-tailwindcss": "^0.5.6"
|
||||
"prettier-plugin-astro": "^0.12.1",
|
||||
"prettier-plugin-tailwindcss": "^0.5.7"
|
||||
}
|
||||
}
|
||||
|
||||
455
pnpm-lock.yaml
generated
455
pnpm-lock.yaml
generated
@@ -6,14 +6,14 @@ settings:
|
||||
|
||||
dependencies:
|
||||
'@astrojs/react':
|
||||
specifier: ^3.0.3
|
||||
version: 3.0.3(@types/react-dom@18.2.14)(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)(vite@4.5.0)
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)(vite@4.5.0)
|
||||
'@astrojs/sitemap':
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.2
|
||||
specifier: ^3.0.3
|
||||
version: 3.0.3
|
||||
'@astrojs/tailwind':
|
||||
specifier: ^5.0.2
|
||||
version: 5.0.2(astro@3.3.3)(tailwindcss@3.3.3)
|
||||
version: 5.0.2(astro@3.5.0)(tailwindcss@3.3.5)
|
||||
'@fingerprintjs/fingerprintjs':
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0
|
||||
@@ -21,17 +21,17 @@ dependencies:
|
||||
specifier: ^0.7.1
|
||||
version: 0.7.1(nanostores@0.9.4)(react@18.2.0)
|
||||
'@types/react':
|
||||
specifier: ^18.2.31
|
||||
version: 18.2.31
|
||||
specifier: ^18.2.37
|
||||
version: 18.2.37
|
||||
'@types/react-dom':
|
||||
specifier: ^18.2.14
|
||||
version: 18.2.14
|
||||
specifier: ^18.2.15
|
||||
version: 18.2.15
|
||||
astro:
|
||||
specifier: ^3.3.3
|
||||
version: 3.3.3(typescript@5.3.0-dev.20231021)
|
||||
specifier: ^3.5.0
|
||||
version: 3.5.0(typescript@5.2.2)
|
||||
astro-compress:
|
||||
specifier: ^2.1.5
|
||||
version: 2.1.5
|
||||
specifier: ^2.2.3
|
||||
version: 2.2.3
|
||||
clsx:
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0
|
||||
@@ -39,23 +39,23 @@ dependencies:
|
||||
specifier: ^2.1.13
|
||||
version: 2.1.13
|
||||
jose:
|
||||
specifier: ^4.15.4
|
||||
version: 4.15.4
|
||||
specifier: ^5.1.0
|
||||
version: 5.1.0
|
||||
js-cookie:
|
||||
specifier: ^3.0.5
|
||||
version: 3.0.5
|
||||
lucide-react:
|
||||
specifier: ^0.288.0
|
||||
version: 0.288.0(react@18.2.0)
|
||||
specifier: ^0.292.0
|
||||
version: 0.292.0(react@18.2.0)
|
||||
nanoid:
|
||||
specifier: ^5.0.2
|
||||
version: 5.0.2
|
||||
specifier: ^5.0.3
|
||||
version: 5.0.3
|
||||
nanostores:
|
||||
specifier: ^0.9.4
|
||||
version: 0.9.4
|
||||
node-html-parser:
|
||||
specifier: ^6.1.10
|
||||
version: 6.1.10
|
||||
specifier: ^6.1.11
|
||||
version: 6.1.11
|
||||
npm-check-updates:
|
||||
specifier: ^16.14.6
|
||||
version: 16.14.6
|
||||
@@ -73,7 +73,7 @@ dependencies:
|
||||
version: 18.2.0(react@18.2.0)
|
||||
reactflow:
|
||||
specifier: ^11.9.4
|
||||
version: 11.9.4(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)
|
||||
version: 11.9.4(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
|
||||
rehype-external-links:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
@@ -84,14 +84,14 @@ dependencies:
|
||||
specifier: ^1.6.6
|
||||
version: 1.6.6
|
||||
tailwind-merge:
|
||||
specifier: ^1.14.0
|
||||
version: 1.14.0
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0
|
||||
tailwindcss:
|
||||
specifier: ^3.3.3
|
||||
version: 3.3.3
|
||||
specifier: ^3.3.5
|
||||
version: 3.3.5
|
||||
zustand:
|
||||
specifier: ^4.4.4
|
||||
version: 4.4.4(@types/react@18.2.31)(react@18.2.0)
|
||||
specifier: ^4.4.6
|
||||
version: 4.4.6(@types/react@18.2.37)(react@18.2.0)
|
||||
|
||||
devDependencies:
|
||||
'@playwright/test':
|
||||
@@ -99,13 +99,13 @@ devDependencies:
|
||||
version: 1.39.0
|
||||
'@tailwindcss/typography':
|
||||
specifier: ^0.5.10
|
||||
version: 0.5.10(tailwindcss@3.3.3)
|
||||
version: 0.5.10(tailwindcss@3.3.5)
|
||||
'@types/js-cookie':
|
||||
specifier: ^3.0.5
|
||||
version: 3.0.5
|
||||
specifier: ^3.0.6
|
||||
version: 3.0.6
|
||||
'@types/prismjs':
|
||||
specifier: ^1.26.2
|
||||
version: 1.26.2
|
||||
specifier: ^1.26.3
|
||||
version: 1.26.3
|
||||
csv-parser:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
@@ -119,17 +119,17 @@ devDependencies:
|
||||
specifier: ^13.0.2
|
||||
version: 13.0.2
|
||||
openai:
|
||||
specifier: ^4.13.0
|
||||
version: 4.13.0
|
||||
specifier: ^4.17.1
|
||||
version: 4.17.1
|
||||
prettier:
|
||||
specifier: ^3.0.3
|
||||
version: 3.0.3
|
||||
prettier-plugin-astro:
|
||||
specifier: ^0.12.0
|
||||
version: 0.12.0
|
||||
specifier: ^0.12.1
|
||||
version: 0.12.1
|
||||
prettier-plugin-tailwindcss:
|
||||
specifier: ^0.5.6
|
||||
version: 0.5.6(prettier-plugin-astro@0.12.0)(prettier@3.0.3)
|
||||
specifier: ^0.5.7
|
||||
version: 0.5.7(prettier-plugin-astro@0.12.1)(prettier@3.0.3)
|
||||
|
||||
packages:
|
||||
|
||||
@@ -149,21 +149,21 @@ packages:
|
||||
resolution: {integrity: sha512-o/ObKgtMzl8SlpIdzaxFnt7SATKPxu4oIP/1NL+HDJRzxfJcAkOTAb/ZKMRyULbz4q+1t2/DAebs2Z1QairkZw==}
|
||||
dev: true
|
||||
|
||||
/@astrojs/compiler@2.2.1:
|
||||
resolution: {integrity: sha512-NJ1lWKzMkyEjE3W5NpPNAVot4/PLF5om/P6ekxNu3iLS05CaYFTcp7WpYMjdCC252b7wkNVAs45FNkVQ+RHW/g==}
|
||||
/@astrojs/compiler@2.3.0:
|
||||
resolution: {integrity: sha512-pxYRAaRdMS6XUll8lbFM+Lr0DI1HKIDT+VpiC+S+9di5H/nmm3znZOgdMlLiMxADot+56eps+M1BvtKfQremXA==}
|
||||
dev: false
|
||||
|
||||
/@astrojs/internal-helpers@0.2.1:
|
||||
resolution: {integrity: sha512-06DD2ZnItMwUnH81LBLco3tWjcZ1lGU9rLCCBaeUCGYe9cI0wKyY2W3kDyoW1I6GmcWgt1fu+D1CTvz+FIKf8A==}
|
||||
dev: false
|
||||
|
||||
/@astrojs/markdown-remark@3.3.0(astro@3.3.3):
|
||||
resolution: {integrity: sha512-ezFzEiZygc/ASe2Eul9v1yrTbNGqSbR348UGNXQ4Dtkx8MYRwfiBfmPm6VnEdfIGkW+bi5qIUReKfc7mPVUkIg==}
|
||||
/@astrojs/markdown-remark@3.4.0(astro@3.5.0):
|
||||
resolution: {integrity: sha512-uzLSKBQ4e70aH8gEbBHZ2pnv/KOJKB3WrXFBOF0U5Uwjcr2LNWeIBLjPRQjA4tbtteELh84YPBHny21mhvBGVA==}
|
||||
peerDependencies:
|
||||
astro: ^3.3.0
|
||||
astro: ^3.0.0
|
||||
dependencies:
|
||||
'@astrojs/prism': 3.0.0
|
||||
astro: 3.3.3(typescript@5.3.0-dev.20231021)
|
||||
astro: 3.5.0(typescript@5.2.2)
|
||||
github-slugger: 2.0.0
|
||||
import-meta-resolve: 3.0.0
|
||||
mdast-util-definitions: 6.0.0
|
||||
@@ -188,8 +188,8 @@ packages:
|
||||
prismjs: 1.29.0
|
||||
dev: false
|
||||
|
||||
/@astrojs/react@3.0.3(@types/react-dom@18.2.14)(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)(vite@4.5.0):
|
||||
resolution: {integrity: sha512-foliIy1whJobo+ZpsvOMS4WCiR0z4/2Seyxth5xMlweVVM+gA1Lqk0GdzE6F0ISUW9CuXrCRS7ZyTNW8SM6vog==}
|
||||
/@astrojs/react@3.0.4(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)(vite@4.5.0):
|
||||
resolution: {integrity: sha512-v+qltrm5nfqqm8C5ymYDBxrGlvNouRr+iCLcUWvNX65abVz8GzUqquhPQjmCTDXphn1Qc558Uxzc4s79l02skw==}
|
||||
engines: {node: '>=18.14.1'}
|
||||
peerDependencies:
|
||||
'@types/react': ^17.0.50 || ^18.0.21
|
||||
@@ -197,8 +197,8 @@ packages:
|
||||
react: ^17.0.2 || ^18.0.0
|
||||
react-dom: ^17.0.2 || ^18.0.0
|
||||
dependencies:
|
||||
'@types/react': 18.2.31
|
||||
'@types/react-dom': 18.2.14
|
||||
'@types/react': 18.2.37
|
||||
'@types/react-dom': 18.2.15
|
||||
'@vitejs/plugin-react': 4.1.0(vite@4.5.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
@@ -208,30 +208,30 @@ packages:
|
||||
- vite
|
||||
dev: false
|
||||
|
||||
/@astrojs/sitemap@3.0.2:
|
||||
resolution: {integrity: sha512-ldxCDc0+QHcq2jH4WJFcz5iBc5/SqotEqpT91dhvoLqoRUT5U21rQ6u6qA4FyRVjpd/0Nt1kFjEsRbwoB8IKiQ==}
|
||||
/@astrojs/sitemap@3.0.3:
|
||||
resolution: {integrity: sha512-+GRKp1yho9dpHBcMcU6JpbL41k0yYZghOkNsMRb8QIRflbGHvd787tdv9oIZ5NJj0SqAuOlqp2UpqLkJXuAe2A==}
|
||||
dependencies:
|
||||
sitemap: 7.1.1
|
||||
zod: 3.21.1
|
||||
zod: 3.22.4
|
||||
dev: false
|
||||
|
||||
/@astrojs/tailwind@5.0.2(astro@3.3.3)(tailwindcss@3.3.3):
|
||||
/@astrojs/tailwind@5.0.2(astro@3.5.0)(tailwindcss@3.3.5):
|
||||
resolution: {integrity: sha512-oXqeqmBlkQmsltrsU9nEWeXOtrZIAHW8dcmX7BCdrjzPnU6dPwWzAwhddNQ9ihKiWwsLnlbwQZIo2CDigcZlIA==}
|
||||
peerDependencies:
|
||||
astro: ^3.2.4
|
||||
tailwindcss: ^3.0.24
|
||||
dependencies:
|
||||
astro: 3.3.3(typescript@5.3.0-dev.20231021)
|
||||
astro: 3.5.0(typescript@5.2.2)
|
||||
autoprefixer: 10.4.16(postcss@8.4.31)
|
||||
postcss: 8.4.31
|
||||
postcss-load-config: 4.0.1(postcss@8.4.31)
|
||||
tailwindcss: 3.3.3
|
||||
tailwindcss: 3.3.5
|
||||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
dev: false
|
||||
|
||||
/@astrojs/telemetry@3.0.3:
|
||||
resolution: {integrity: sha512-j19Cf5mfyLt9hxgJ9W/FMdAA5Lovfp7/CINNB/7V71GqvygnL7KXhRC3TzfB+PsVQcBtgWZzCXhUWRbmJ64Raw==}
|
||||
/@astrojs/telemetry@3.0.4:
|
||||
resolution: {integrity: sha512-A+0c7k/Xy293xx6odsYZuXiaHO0PL+bnDoXOc47sGDF5ffIKdKQGRPFl2NMlCF4L0NqN4Ynbgnaip+pPF0s7pQ==}
|
||||
engines: {node: '>=18.14.1'}
|
||||
dependencies:
|
||||
ci-info: 3.9.0
|
||||
@@ -456,6 +456,13 @@ packages:
|
||||
'@babel/types': 7.23.0
|
||||
dev: false
|
||||
|
||||
/@babel/runtime@7.23.2:
|
||||
resolution: {integrity: sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
regenerator-runtime: 0.14.0
|
||||
dev: false
|
||||
|
||||
/@babel/template@7.22.15:
|
||||
resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -1067,39 +1074,39 @@ packages:
|
||||
config-chain: 1.1.13
|
||||
dev: false
|
||||
|
||||
/@reactflow/background@11.3.4(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0):
|
||||
/@reactflow/background@11.3.4(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-bgwvqWxF09chwmdkyClpYEMaewBspdwjgLbbFlLf4SpWPFMYyuvCBQrcISsvy/EDEWO9i3Uj9ktgGAhvtSQsmA==}
|
||||
peerDependencies:
|
||||
react: '>=17'
|
||||
react-dom: '>=17'
|
||||
dependencies:
|
||||
'@reactflow/core': 11.9.4(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@reactflow/core': 11.9.4(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
|
||||
classcat: 5.0.4
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
zustand: 4.4.4(@types/react@18.2.31)(react@18.2.0)
|
||||
zustand: 4.4.6(@types/react@18.2.37)(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
- immer
|
||||
dev: false
|
||||
|
||||
/@reactflow/controls@11.2.4(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0):
|
||||
/@reactflow/controls@11.2.4(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-x6e5p9iHjC6gd+4SoZ3DOOp0F1MefGKQ8hT6yPVdqxfo1+rV2WhrWvrX/MCoEu12Dp7457LdLfa0giy3aho8tQ==}
|
||||
peerDependencies:
|
||||
react: '>=17'
|
||||
react-dom: '>=17'
|
||||
dependencies:
|
||||
'@reactflow/core': 11.9.4(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@reactflow/core': 11.9.4(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
|
||||
classcat: 5.0.4
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
zustand: 4.4.4(@types/react@18.2.31)(react@18.2.0)
|
||||
zustand: 4.4.6(@types/react@18.2.37)(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
- immer
|
||||
dev: false
|
||||
|
||||
/@reactflow/core@11.9.4(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0):
|
||||
/@reactflow/core@11.9.4(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Ko7nKPOYalwDTTbRHi2+QXDiidSAcpUzGN3G+0B+QysLZkcaPCkpkMjjHiDC4c/Z1BJBzs1FRJg/T6BXaBnYkg==}
|
||||
peerDependencies:
|
||||
react: '>=17'
|
||||
@@ -1115,19 +1122,19 @@ packages:
|
||||
d3-zoom: 3.0.0
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
zustand: 4.4.4(@types/react@18.2.31)(react@18.2.0)
|
||||
zustand: 4.4.6(@types/react@18.2.37)(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
- immer
|
||||
dev: false
|
||||
|
||||
/@reactflow/minimap@11.7.4(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0):
|
||||
/@reactflow/minimap@11.7.4(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Jo1R+uDey9IV7O2s3m0gK2+cZpg9M8hq2EZJb3NGfOSzMAPhj3mby0fNJIgTzycreuht0TpA51c2YfjGI3YIOw==}
|
||||
peerDependencies:
|
||||
react: '>=17'
|
||||
react-dom: '>=17'
|
||||
dependencies:
|
||||
'@reactflow/core': 11.9.4(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@reactflow/core': 11.9.4(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@types/d3-selection': 3.0.8
|
||||
'@types/d3-zoom': 3.0.6
|
||||
classcat: 5.0.4
|
||||
@@ -1135,41 +1142,41 @@ packages:
|
||||
d3-zoom: 3.0.0
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
zustand: 4.4.4(@types/react@18.2.31)(react@18.2.0)
|
||||
zustand: 4.4.6(@types/react@18.2.37)(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
- immer
|
||||
dev: false
|
||||
|
||||
/@reactflow/node-resizer@2.2.4(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0):
|
||||
/@reactflow/node-resizer@2.2.4(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-+p271/hAsM5M1+RQTWW/02pbNkCHeGXwxGimIlL1tMIagyuko0NX2vOz2B8jxJnPKlF09Wj18BcXBNUm3nDcSg==}
|
||||
peerDependencies:
|
||||
react: '>=17'
|
||||
react-dom: '>=17'
|
||||
dependencies:
|
||||
'@reactflow/core': 11.9.4(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@reactflow/core': 11.9.4(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
|
||||
classcat: 5.0.4
|
||||
d3-drag: 3.0.0
|
||||
d3-selection: 3.0.0
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
zustand: 4.4.4(@types/react@18.2.31)(react@18.2.0)
|
||||
zustand: 4.4.6(@types/react@18.2.37)(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
- immer
|
||||
dev: false
|
||||
|
||||
/@reactflow/node-toolbar@1.3.4(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0):
|
||||
/@reactflow/node-toolbar@1.3.4(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-TfcmpXHRBb2mUfzKGjburiU6FWqRME9pPFs1OwIC1z5e9BjupQhNDEKEk8XHi7PKL/mAiDfwuGXaM1BVVFuPqw==}
|
||||
peerDependencies:
|
||||
react: '>=17'
|
||||
react-dom: '>=17'
|
||||
dependencies:
|
||||
'@reactflow/core': 11.9.4(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@reactflow/core': 11.9.4(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
|
||||
classcat: 5.0.4
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
zustand: 4.4.4(@types/react@18.2.31)(react@18.2.0)
|
||||
zustand: 4.4.6(@types/react@18.2.37)(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
- immer
|
||||
@@ -1220,7 +1227,7 @@ packages:
|
||||
defer-to-connect: 2.0.1
|
||||
dev: false
|
||||
|
||||
/@tailwindcss/typography@0.5.10(tailwindcss@3.3.3):
|
||||
/@tailwindcss/typography@0.5.10(tailwindcss@3.3.5):
|
||||
resolution: {integrity: sha512-Pe8BuPJQJd3FfRnm6H0ulKIGoMEQS+Vq01R6M5aCrFB/ccR/shT+0kXLjouGC1gFLm9hopTFN+DMP0pfwRWzPw==}
|
||||
peerDependencies:
|
||||
tailwindcss: '>=3.0.0 || insiders'
|
||||
@@ -1229,7 +1236,7 @@ packages:
|
||||
lodash.isplainobject: 4.0.6
|
||||
lodash.merge: 4.6.2
|
||||
postcss-selector-parser: 6.0.10
|
||||
tailwindcss: 3.3.3
|
||||
tailwindcss: 3.3.5
|
||||
dev: true
|
||||
|
||||
/@tootallnate/once@2.0.0:
|
||||
@@ -1507,8 +1514,8 @@ packages:
|
||||
resolution: {integrity: sha512-V46MYLFp08Wf2mmaBhvgjStM3tPa+2GAdy/iqoX+noX1//zje2x4XmrIU0cAwyClATsTmahbtoQ2EwP7I5WSiA==}
|
||||
dev: false
|
||||
|
||||
/@types/js-cookie@3.0.5:
|
||||
resolution: {integrity: sha512-dtLshqoiGRDHbHueIT9sjkd2F4tW1qPSX2xKAQK8p1e6pM+Z913GM1shv7dOqqasEMYbC5zEaClJomQe8OtQLA==}
|
||||
/@types/js-cookie@3.0.6:
|
||||
resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==}
|
||||
dev: true
|
||||
|
||||
/@types/mdast@3.0.14:
|
||||
@@ -1536,7 +1543,7 @@ packages:
|
||||
/@types/node-fetch@2.6.7:
|
||||
resolution: {integrity: sha512-lX17GZVpJ/fuCjguZ5b3TjEbSENxmEk1B2z02yoXSK9WMEWRivhdSY73wWMn6bpcCDAOh6qAdktpKHIlkDk2lg==}
|
||||
dependencies:
|
||||
'@types/node': 18.18.6
|
||||
'@types/node': 20.8.9
|
||||
form-data: 4.0.0
|
||||
dev: true
|
||||
|
||||
@@ -1548,32 +1555,31 @@ packages:
|
||||
resolution: {integrity: sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==}
|
||||
dev: true
|
||||
|
||||
/@types/node@20.8.7:
|
||||
resolution: {integrity: sha512-21TKHHh3eUHIi2MloeptJWALuCu5H7HQTdTrWIFReA8ad+aggoX+lRes3ex7/FtpC+sVUpFMQ+QTfYr74mruiQ==}
|
||||
/@types/node@20.8.9:
|
||||
resolution: {integrity: sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==}
|
||||
dependencies:
|
||||
undici-types: 5.25.3
|
||||
dev: false
|
||||
undici-types: 5.26.5
|
||||
|
||||
/@types/parse5@6.0.3:
|
||||
resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
|
||||
dev: false
|
||||
|
||||
/@types/prismjs@1.26.2:
|
||||
resolution: {integrity: sha512-/r7Cp7iUIk7gts26mHXD66geUC+2Fo26TZYjQK6Nr4LDfi6lmdRmMqM0oPwfiMhUwoBAOFe8GstKi2pf6hZvwA==}
|
||||
/@types/prismjs@1.26.3:
|
||||
resolution: {integrity: sha512-A0D0aTXvjlqJ5ZILMz3rNfDBOx9hHxLZYv2by47Sm/pqW35zzjusrZTryatjN/Rf8Us2gZrJD+KeHbUSTux1Cw==}
|
||||
dev: true
|
||||
|
||||
/@types/prop-types@15.7.9:
|
||||
resolution: {integrity: sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==}
|
||||
dev: false
|
||||
|
||||
/@types/react-dom@18.2.14:
|
||||
resolution: {integrity: sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ==}
|
||||
/@types/react-dom@18.2.15:
|
||||
resolution: {integrity: sha512-HWMdW+7r7MR5+PZqJF6YFNSCtjz1T0dsvo/f1BV6HkV+6erD/nA7wd9NM00KVG83zf2nJ7uATPO9ttdIPvi3gg==}
|
||||
dependencies:
|
||||
'@types/react': 18.2.31
|
||||
'@types/react': 18.2.37
|
||||
dev: false
|
||||
|
||||
/@types/react@18.2.31:
|
||||
resolution: {integrity: sha512-c2UnPv548q+5DFh03y8lEDeMfDwBn9G3dRwfkrxQMo/dOtRHUUO57k6pHvBIfH/VF4Nh+98mZ5aaSe+2echD5g==}
|
||||
/@types/react@18.2.37:
|
||||
resolution: {integrity: sha512-RGAYMi2bhRgEXT3f4B92WTohopH6bIXw05FuGlmJEnv/omEn190+QYEIYxIAuIBdKgboYYdVved2p1AxZVQnaw==}
|
||||
dependencies:
|
||||
'@types/prop-types': 15.7.9
|
||||
'@types/scheduler': 0.16.5
|
||||
@@ -1583,7 +1589,7 @@ packages:
|
||||
/@types/sax@1.2.6:
|
||||
resolution: {integrity: sha512-A1mpYCYu1aHFayy8XKN57ebXeAbh9oQIZ1wXcno6b1ESUAfMBDMx7mf/QGlYwcMRaFryh9YBuH03i/3FlPGDkQ==}
|
||||
dependencies:
|
||||
'@types/node': 17.0.45
|
||||
'@types/node': 20.8.9
|
||||
dev: false
|
||||
|
||||
/@types/scheduler@0.16.5:
|
||||
@@ -1752,29 +1758,30 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/astro-compress@2.1.5:
|
||||
resolution: {integrity: sha512-aoqGm2zbv8LMTl8tktiyV/BfTs4GrKb7SFYQ6Fuv7BqpYkn33yrxs0Qz+CFpXL72YUGmQuWw3vuUi5IA3S4BzA==}
|
||||
/astro-compress@2.2.3:
|
||||
resolution: {integrity: sha512-aU4yiTOsnrFdMQnfzxL3bxTCaa/4vIvGXR8e+Ngxr6jpZvzV1pOy7hIthzmt4vjHe6m4VfRSZwtc3/x16/fJcQ==}
|
||||
dependencies:
|
||||
'@types/csso': 5.0.2
|
||||
'@types/html-minifier-terser': 7.0.1
|
||||
csso: 5.0.5
|
||||
files-pipe: 2.1.1
|
||||
files-pipe: 2.1.2
|
||||
html-minifier-terser: 7.2.0
|
||||
lightningcss: 1.22.0
|
||||
sharp: 0.32.6
|
||||
svgo: 3.0.2
|
||||
terser: 5.22.0
|
||||
typescript-esbuild: 0.2.25
|
||||
terser: 5.24.0
|
||||
typescript-esbuild: 0.3.1
|
||||
dev: false
|
||||
|
||||
/astro@3.3.3(typescript@5.3.0-dev.20231021):
|
||||
resolution: {integrity: sha512-FZkv5nJfa2KADzwo8m6fytWzzhO3Uw/EOvxmBT2E1OW/dWUgIKbZd59TY3816gZl3le5Ct5amSAkaxcQghbUZA==}
|
||||
/astro@3.5.0(typescript@5.2.2):
|
||||
resolution: {integrity: sha512-Wwu9gXIlxqCUEY6Bj9e08TeBm7I/CupyzHrWTNSPL+iwrTo/3/pL+itCeYz2u84jRygBgd2oPEN0FbK/sjj+uQ==}
|
||||
engines: {node: '>=18.14.1', npm: '>=6.14.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@astrojs/compiler': 2.2.1
|
||||
'@astrojs/compiler': 2.3.0
|
||||
'@astrojs/internal-helpers': 0.2.1
|
||||
'@astrojs/markdown-remark': 3.3.0(astro@3.3.3)
|
||||
'@astrojs/telemetry': 3.0.3
|
||||
'@astrojs/markdown-remark': 3.4.0(astro@3.5.0)
|
||||
'@astrojs/telemetry': 3.0.4
|
||||
'@babel/core': 7.23.2
|
||||
'@babel/generator': 7.23.0
|
||||
'@babel/parser': 7.23.0
|
||||
@@ -1805,9 +1812,11 @@ packages:
|
||||
js-yaml: 4.1.0
|
||||
kleur: 4.1.5
|
||||
magic-string: 0.30.5
|
||||
mdast-util-to-hast: 12.3.0
|
||||
mime: 3.0.0
|
||||
ora: 7.0.1
|
||||
p-limit: 4.0.0
|
||||
p-queue: 7.4.1
|
||||
path-to-regexp: 6.2.1
|
||||
preferred-pm: 3.1.2
|
||||
probe-image-size: 7.2.3
|
||||
@@ -1819,14 +1828,14 @@ packages:
|
||||
shikiji: 0.6.10
|
||||
string-width: 6.1.0
|
||||
strip-ansi: 7.1.0
|
||||
tsconfck: 3.0.0(typescript@5.3.0-dev.20231021)
|
||||
tsconfck: 3.0.0(typescript@5.2.2)
|
||||
unist-util-visit: 4.1.2
|
||||
vfile: 5.3.7
|
||||
vite: 4.5.0
|
||||
vitefu: 0.2.5(vite@4.5.0)
|
||||
which-pm: 2.1.1
|
||||
yargs-parser: 21.1.1
|
||||
zod: 3.21.1
|
||||
zod: 3.22.4
|
||||
optionalDependencies:
|
||||
sharp: 0.32.6
|
||||
transitivePeerDependencies:
|
||||
@@ -2480,6 +2489,12 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/detect-libc@1.0.3:
|
||||
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
|
||||
engines: {node: '>=0.10'}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/detect-libc@2.0.2:
|
||||
resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -2743,6 +2758,10 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/eventemitter3@5.0.1:
|
||||
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
|
||||
dev: false
|
||||
|
||||
/execa@8.0.1:
|
||||
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
|
||||
engines: {node: '>=16.17'}
|
||||
@@ -2815,10 +2834,10 @@ packages:
|
||||
trim-repeated: 1.0.0
|
||||
dev: true
|
||||
|
||||
/files-pipe@2.1.1:
|
||||
resolution: {integrity: sha512-grvkOxmaPpu7EHI4BeFpVAuZiNfbaftmKhy8ly5/miUCNYkIfrqiQB+ZG9jGrdlefYqM3MiFfE9GUw7g7sGd/A==}
|
||||
/files-pipe@2.1.2:
|
||||
resolution: {integrity: sha512-QWmVUJ4swy7Jan9x3VPUVjoGdUw+YZRY4j5AaqArOdRF5lX8vgIPdD+OzUqWzJ/pvjHY2zU43b5s2lb1hiSNlA==}
|
||||
dependencies:
|
||||
'@types/node': 20.8.7
|
||||
'@types/node': 20.8.9
|
||||
deepmerge-ts: 5.1.0
|
||||
fast-glob: 3.3.1
|
||||
dev: false
|
||||
@@ -3351,7 +3370,7 @@ packages:
|
||||
entities: 4.5.0
|
||||
param-case: 3.0.4
|
||||
relateurl: 0.2.7
|
||||
terser: 5.22.0
|
||||
terser: 5.24.0
|
||||
dev: false
|
||||
|
||||
/html-void-elements@2.0.1:
|
||||
@@ -3640,8 +3659,8 @@ packages:
|
||||
resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==}
|
||||
dev: false
|
||||
|
||||
/jose@4.15.4:
|
||||
resolution: {integrity: sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==}
|
||||
/jose@5.1.0:
|
||||
resolution: {integrity: sha512-H+RVqxA6apaJ0rcQYupKYhos7uosAiF42gUcWZiwhICWMphDULFj/CRr1R0tV/JCv9DEeJaSyYYpc9luHHNT4g==}
|
||||
dev: false
|
||||
|
||||
/js-cookie@3.0.5:
|
||||
@@ -3742,6 +3761,104 @@ packages:
|
||||
package-json: 8.1.1
|
||||
dev: false
|
||||
|
||||
/lightningcss-darwin-arm64@1.22.0:
|
||||
resolution: {integrity: sha512-aH2be3nNny+It5YEVm8tBSSdRlBVWQV8m2oJ7dESiYRzyY/E/bQUe2xlw5caaMuhlM9aoTMtOH25yzMhir0qPg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/lightningcss-darwin-x64@1.22.0:
|
||||
resolution: {integrity: sha512-9KHRFA0Y6mNxRHeoQMp0YaI0R0O2kOgUlYPRjuasU4d+pI8NRhVn9bt0yX9VPs5ibWX1RbDViSPtGJvYYrfVAQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/lightningcss-freebsd-x64@1.22.0:
|
||||
resolution: {integrity: sha512-xaYL3xperGwD85rQioDb52ozF3NAJb+9wrge3jD9lxGffplu0Mn35rXMptB8Uc2N9Mw1i3Bvl7+z1evlqVl7ww==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/lightningcss-linux-arm-gnueabihf@1.22.0:
|
||||
resolution: {integrity: sha512-epQGvXIjOuxrZpMpMnRjK54ZqzhiHhCPLtHvw2fb6NeK2kK9YtF0wqmeTBiQ1AkbWfnnXGTstYaFNiadNK+StQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/lightningcss-linux-arm64-gnu@1.22.0:
|
||||
resolution: {integrity: sha512-AArGtKSY4DGTA8xP8SDyNyKtpsUl1Rzq6FW4JomeyUQ4nBrR71uPChksTpj3gmWuGhZeRKLeCUI1DBid/zhChg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/lightningcss-linux-arm64-musl@1.22.0:
|
||||
resolution: {integrity: sha512-RRraNgP8hnBPhInTTUdlFm+z16C/ghbxBG51Sw00hd7HUyKmEUKRozyc5od+/N6pOrX/bIh5vIbtMXIxsos0lg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/lightningcss-linux-x64-gnu@1.22.0:
|
||||
resolution: {integrity: sha512-grdrhYGRi2KrR+bsXJVI0myRADqyA7ekprGxiuK5QRNkv7kj3Yq1fERDNyzZvjisHwKUi29sYMClscbtl+/Zpw==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/lightningcss-linux-x64-musl@1.22.0:
|
||||
resolution: {integrity: sha512-t5f90X+iQUtIyR56oXIHMBUyQFX/zwmPt72E6Dane3P8KNGlkijTg2I75XVQS860gNoEFzV7Mm5ArRRA7u5CAQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/lightningcss-win32-x64-msvc@1.22.0:
|
||||
resolution: {integrity: sha512-64HTDtOOZE9PUCZJiZZQpyqXBbdby1lnztBccnqh+NtbKxjnGzP92R2ngcgeuqMPecMNqNWxgoWgTGpC+yN5Sw==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/lightningcss@1.22.0:
|
||||
resolution: {integrity: sha512-+z0qvwRVzs4XGRXelnWRNwqsXUx8k3bSkbP8vD42kYKSk3z9OM2P3e/gagT7ei/gwh8DTS80LZOFZV6lm8Z8Fg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
dependencies:
|
||||
detect-libc: 1.0.3
|
||||
optionalDependencies:
|
||||
lightningcss-darwin-arm64: 1.22.0
|
||||
lightningcss-darwin-x64: 1.22.0
|
||||
lightningcss-freebsd-x64: 1.22.0
|
||||
lightningcss-linux-arm-gnueabihf: 1.22.0
|
||||
lightningcss-linux-arm64-gnu: 1.22.0
|
||||
lightningcss-linux-arm64-musl: 1.22.0
|
||||
lightningcss-linux-x64-gnu: 1.22.0
|
||||
lightningcss-linux-x64-musl: 1.22.0
|
||||
lightningcss-win32-x64-msvc: 1.22.0
|
||||
dev: false
|
||||
|
||||
/lilconfig@2.1.0:
|
||||
resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -3846,8 +3963,8 @@ packages:
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/lucide-react@0.288.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-ikhb/9LOkq9orPoLV9lLC4UYyoXQycBhIgH7H59ahOkk0mkcAqkD52m84RXedE/qVqZHW8rEJquInT4xGmsNqw==}
|
||||
/lucide-react@0.292.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-rRgUkpEHWpa5VCT66YscInCQmQuPCB1RFRzkkxMxg4b+jaL0V12E3riWWR2Sh5OIiUhCwGW/ZExuEO4Az32E6Q==}
|
||||
peerDependencies:
|
||||
react: ^16.5.1 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
@@ -4539,8 +4656,8 @@ packages:
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
/nanoid@5.0.2:
|
||||
resolution: {integrity: sha512-2ustYUX1R2rL/Br5B/FMhi8d5/QzvkJ912rBYxskcpu0myTHzSZfTr1LAS2Sm7jxRUObRrSBFoyzwAhL49aVSg==}
|
||||
/nanoid@5.0.3:
|
||||
resolution: {integrity: sha512-I7X2b22cxA4LIHXPSqbBCEQSL+1wv8TuoefejsX4HFWyC6jc5JG7CEaxOltiKjc1M+YCS2YkrZZcj4+dytw9GA==}
|
||||
engines: {node: ^18 || >=20}
|
||||
hasBin: true
|
||||
dev: false
|
||||
@@ -4632,8 +4749,8 @@ packages:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/node-html-parser@6.1.10:
|
||||
resolution: {integrity: sha512-6/uWdWxjQWQ7tMcFK2wWlrflsQUzh1HsEzlIf2j5+TtzfhT2yUvg3DwZYAmjEHeR3uX74ko7exjHW69J0tOzIg==}
|
||||
/node-html-parser@6.1.11:
|
||||
resolution: {integrity: sha512-FAgwwZ6h0DSDWxfD0Iq1tsDcBCxdJB1nXpLPPxX8YyVWzbfCjKWEzaynF4gZZ/8hziUmp7ZSaKylcn0iKhufUQ==}
|
||||
dependencies:
|
||||
css-select: 5.1.0
|
||||
he: 1.2.0
|
||||
@@ -4828,8 +4945,8 @@ packages:
|
||||
mimic-fn: 4.0.0
|
||||
dev: false
|
||||
|
||||
/openai@4.13.0:
|
||||
resolution: {integrity: sha512-EPqHcB0got9cXDZmQae1KytgA4YWtTnUc7tV8hlahZtcO70DMa4kiaXoxnutj9lwmeKQO7ntG+6pmXtrCMejuQ==}
|
||||
/openai@4.17.1:
|
||||
resolution: {integrity: sha512-8IrjeT9B63/6rKA2NI/0LFnjRA37G+NvNwbRvcEH9+AjAahReSMtT8xs4J+ClhzdUL9d91GJymgbo9RJt+HQuQ==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@types/node': 18.18.6
|
||||
@@ -4905,6 +5022,19 @@ packages:
|
||||
aggregate-error: 3.1.0
|
||||
dev: false
|
||||
|
||||
/p-queue@7.4.1:
|
||||
resolution: {integrity: sha512-vRpMXmIkYF2/1hLBKisKeVYJZ8S2tZ0zEAmIJgdVKP2nq0nh4qCdf8bgw+ZgKrkh71AOCaqzwbJJk1WtdcF3VA==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
eventemitter3: 5.0.1
|
||||
p-timeout: 5.1.0
|
||||
dev: false
|
||||
|
||||
/p-timeout@5.1.0:
|
||||
resolution: {integrity: sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/p-try@2.2.0:
|
||||
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -5177,8 +5307,8 @@ packages:
|
||||
which-pm: 2.0.0
|
||||
dev: false
|
||||
|
||||
/prettier-plugin-astro@0.12.0:
|
||||
resolution: {integrity: sha512-8E+9YQR6/5CPZJs8XsfBw579zrwZkc0Wb7x0fRVm/51JC8Iys4lBw4ecV8fHwpbQnzve86TUa4fJ08BJzqfWnA==}
|
||||
/prettier-plugin-astro@0.12.1:
|
||||
resolution: {integrity: sha512-1mlNIU/cV+25oB4z5wXzOz2fSDcawG3MsVUwgw2i8VSy7voLSENMSpR1juu3U5MAVUo3owuyax11QuylbpuqOQ==}
|
||||
engines: {node: ^14.15.0 || >=16.0.0}
|
||||
dependencies:
|
||||
'@astrojs/compiler': 1.8.2
|
||||
@@ -5186,8 +5316,8 @@ packages:
|
||||
sass-formatter: 0.7.8
|
||||
dev: true
|
||||
|
||||
/prettier-plugin-tailwindcss@0.5.6(prettier-plugin-astro@0.12.0)(prettier@3.0.3):
|
||||
resolution: {integrity: sha512-2Xgb+GQlkPAUCFi3sV+NOYcSI5XgduvDBL2Zt/hwJudeKXkyvRS65c38SB0yb9UB40+1rL83I6m0RtlOQ8eHdg==}
|
||||
/prettier-plugin-tailwindcss@0.5.7(prettier-plugin-astro@0.12.1)(prettier@3.0.3):
|
||||
resolution: {integrity: sha512-4v6uESAgwCni6YF6DwJlRaDjg9Z+al5zM4JfngcazMy4WEf/XkPS5TEQjbD+DZ5iNuG6RrKQLa/HuX2SYzC3kQ==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
peerDependencies:
|
||||
'@ianvs/prettier-plugin-sort-imports': '*'
|
||||
@@ -5239,7 +5369,7 @@ packages:
|
||||
optional: true
|
||||
dependencies:
|
||||
prettier: 3.0.3
|
||||
prettier-plugin-astro: 0.12.0
|
||||
prettier-plugin-astro: 0.12.1
|
||||
dev: true
|
||||
|
||||
/prettier@3.0.3:
|
||||
@@ -5393,18 +5523,18 @@ packages:
|
||||
loose-envify: 1.4.0
|
||||
dev: false
|
||||
|
||||
/reactflow@11.9.4(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0):
|
||||
/reactflow@11.9.4(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-IHAKBkJngNvU9y1vZ5Nw9rvA3Z+zc9geTgQQIi9qq9Y9knGLlDDr9KfsjbFMew9AycAAgVg8TvBEakF4IT5lqg==}
|
||||
peerDependencies:
|
||||
react: '>=17'
|
||||
react-dom: '>=17'
|
||||
dependencies:
|
||||
'@reactflow/background': 11.3.4(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@reactflow/controls': 11.2.4(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@reactflow/core': 11.9.4(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@reactflow/minimap': 11.7.4(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@reactflow/node-resizer': 2.2.4(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@reactflow/node-toolbar': 1.3.4(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@reactflow/background': 11.3.4(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@reactflow/controls': 11.2.4(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@reactflow/core': 11.9.4(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@reactflow/minimap': 11.7.4(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@reactflow/node-resizer': 2.2.4(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@reactflow/node-toolbar': 1.3.4(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
@@ -5450,6 +5580,10 @@ packages:
|
||||
dependencies:
|
||||
picomatch: 2.3.1
|
||||
|
||||
/regenerator-runtime@0.14.0:
|
||||
resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==}
|
||||
dev: false
|
||||
|
||||
/registry-auth-token@5.0.2:
|
||||
resolution: {integrity: sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -6090,12 +6224,14 @@ packages:
|
||||
picocolors: 1.0.0
|
||||
dev: false
|
||||
|
||||
/tailwind-merge@1.14.0:
|
||||
resolution: {integrity: sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==}
|
||||
/tailwind-merge@2.0.0:
|
||||
resolution: {integrity: sha512-WO8qghn9yhsldLSg80au+3/gY9E4hFxIvQ3qOmlpXnqpDKoMruKfi/56BbbMg6fHTQJ9QD3cc79PoWqlaQE4rw==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.23.2
|
||||
dev: false
|
||||
|
||||
/tailwindcss@3.3.3:
|
||||
resolution: {integrity: sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==}
|
||||
/tailwindcss@3.3.5:
|
||||
resolution: {integrity: sha512-5SEZU4J7pxZgSkv7FP1zY8i2TIAOooNZ1e/OGtxIEv6GltpoiXUqWvLy89+a10qYTB1N5Ifkuw9lqQkN9sscvA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
@@ -6172,8 +6308,8 @@ packages:
|
||||
yallist: 4.0.0
|
||||
dev: false
|
||||
|
||||
/terser@5.22.0:
|
||||
resolution: {integrity: sha512-hHZVLgRA2z4NWcN6aS5rQDc+7Dcy58HOf2zbYwmFcQ+ua3h6eEFf5lIDKTzbWwlazPyOZsFQO8V80/IjVNExEw==}
|
||||
/terser@5.24.0:
|
||||
resolution: {integrity: sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
@@ -6227,7 +6363,7 @@ packages:
|
||||
/ts-interface-checker@0.1.13:
|
||||
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
||||
|
||||
/tsconfck@3.0.0(typescript@5.3.0-dev.20231021):
|
||||
/tsconfck@3.0.0(typescript@5.2.2):
|
||||
resolution: {integrity: sha512-w3wnsIrJNi7avf4Zb0VjOoodoO0woEqGgZGQm+LHH9przdUI+XDKsWAXwxHA1DaRTjeuZNcregSzr7RaA8zG9A==}
|
||||
engines: {node: ^18 || >=20}
|
||||
hasBin: true
|
||||
@@ -6237,7 +6373,7 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
typescript: 5.3.0-dev.20231021
|
||||
typescript: 5.2.2
|
||||
dev: false
|
||||
|
||||
/tslib@2.6.2:
|
||||
@@ -6286,7 +6422,7 @@ packages:
|
||||
peerDependencies:
|
||||
typedoc: ^0.25.0
|
||||
dependencies:
|
||||
typedoc: 0.25.2(typescript@5.3.0-dev.20231021)
|
||||
typedoc: 0.25.2(typescript@5.2.2)
|
||||
typescript: 5.2.2
|
||||
dev: false
|
||||
|
||||
@@ -6295,7 +6431,7 @@ packages:
|
||||
peerDependencies:
|
||||
typedoc: '>= 0.23.14 || 0.24.x || 0.25.x'
|
||||
dependencies:
|
||||
typedoc: 0.25.2(typescript@5.3.0-dev.20231021)
|
||||
typedoc: 0.25.2(typescript@5.2.2)
|
||||
dev: false
|
||||
|
||||
/typedoc-plugin-merge-modules@5.1.0(typedoc@0.25.2):
|
||||
@@ -6303,7 +6439,7 @@ packages:
|
||||
peerDependencies:
|
||||
typedoc: 0.24.x || 0.25.x
|
||||
dependencies:
|
||||
typedoc: 0.25.2(typescript@5.3.0-dev.20231021)
|
||||
typedoc: 0.25.2(typescript@5.2.2)
|
||||
dev: false
|
||||
|
||||
/typedoc-plugin-remove-references@0.0.6:
|
||||
@@ -6315,7 +6451,7 @@ packages:
|
||||
peerDependencies:
|
||||
typedoc: 0.22.x || 0.23.x || 0.24.x || 0.25.x
|
||||
dependencies:
|
||||
typedoc: 0.25.2(typescript@5.3.0-dev.20231021)
|
||||
typedoc: 0.25.2(typescript@5.2.2)
|
||||
dev: false
|
||||
|
||||
/typedoc-plugin-zod@1.1.0(typedoc@0.25.2):
|
||||
@@ -6323,10 +6459,10 @@ packages:
|
||||
peerDependencies:
|
||||
typedoc: 0.23.x || 0.24.x || 0.25.x
|
||||
dependencies:
|
||||
typedoc: 0.25.2(typescript@5.3.0-dev.20231021)
|
||||
typedoc: 0.25.2(typescript@5.2.2)
|
||||
dev: false
|
||||
|
||||
/typedoc@0.25.2(typescript@5.3.0-dev.20231021):
|
||||
/typedoc@0.25.2(typescript@5.2.2):
|
||||
resolution: {integrity: sha512-286F7BeATBiWe/qC4PCOCKlSTwfnsLbC/4cZ68oGBbvAqb9vV33quEOXx7q176OXotD+JdEerdQ1OZGJ818lnA==}
|
||||
engines: {node: '>= 16'}
|
||||
hasBin: true
|
||||
@@ -6337,27 +6473,27 @@ packages:
|
||||
marked: 4.3.0
|
||||
minimatch: 9.0.3
|
||||
shiki: 0.14.5
|
||||
typescript: 5.3.0-dev.20231021
|
||||
typescript: 5.2.2
|
||||
dev: false
|
||||
|
||||
/typescript-esbuild@0.2.25:
|
||||
resolution: {integrity: sha512-sOpKewLkTFG7ebqSTZNsHdnvoZ7X8jy7F1NjBYAdwiVfKFOR7fLuweYd17hRUTqcAf2wp28BJNqJ6c3WLgrMfw==}
|
||||
/typescript-esbuild@0.3.1:
|
||||
resolution: {integrity: sha512-ZJwbrzS8Jv2OTAPbFEvD85CbjRLVYbjUV51JPn3THe/bbh2pY+P9UVVZ8QUvHag8kpqEwgrdhVhYgrdgUkC/lw==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@types/node': 20.8.7
|
||||
'@types/node': 20.8.9
|
||||
commander: 11.1.0
|
||||
deepmerge-ts: 5.1.0
|
||||
esbuild: 0.19.5
|
||||
esbuild-plugin-copy: 2.1.1(esbuild@0.19.5)
|
||||
fast-glob: 3.3.1
|
||||
typedoc: 0.25.2(typescript@5.3.0-dev.20231021)
|
||||
typedoc: 0.25.2(typescript@5.2.2)
|
||||
typedoc-plugin-keywords: 1.5.0(typedoc@0.25.2)
|
||||
typedoc-plugin-mdn-links: 3.1.0(typedoc@0.25.2)
|
||||
typedoc-plugin-merge-modules: 5.1.0(typedoc@0.25.2)
|
||||
typedoc-plugin-remove-references: 0.0.6
|
||||
typedoc-plugin-rename-defaults: 0.6.7(typedoc@0.25.2)
|
||||
typedoc-plugin-zod: 1.1.0(typedoc@0.25.2)
|
||||
typescript: 5.3.0-dev.20231021
|
||||
typescript: 5.2.2
|
||||
dev: false
|
||||
|
||||
/typescript@5.2.2:
|
||||
@@ -6366,12 +6502,6 @@ packages:
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/typescript@5.3.0-dev.20231021:
|
||||
resolution: {integrity: sha512-Q9le8GTluHWR1d6lo8anl5+ImJrZ9KApSpM4yEkDIMVcKueCC6q+Wz/kDL6FXPcO8odWHhDQD4vImed7KDp9Fw==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/uc.micro@1.0.6:
|
||||
resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==}
|
||||
dev: true
|
||||
@@ -6380,9 +6510,8 @@ packages:
|
||||
resolution: {integrity: sha512-qh4mBffhlkiXwDAOxvSGxhL0QEQsTbnP9BozOK3OYPEGvPvdWzvAUaXNtUSMdNsKDtuyjEbyVUPFZ52SSLhLqw==}
|
||||
dev: false
|
||||
|
||||
/undici-types@5.25.3:
|
||||
resolution: {integrity: sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==}
|
||||
dev: false
|
||||
/undici-types@5.26.5:
|
||||
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
||||
|
||||
/unherit@3.0.1:
|
||||
resolution: {integrity: sha512-akOOQ/Yln8a2sgcLj4U0Jmx0R5jpIg2IUyRrWOzmEbjBtGzBdHtSeFKgoEcoH4KYIG/Pb8GQ/BwtYm0GCq1Sqg==}
|
||||
@@ -6816,12 +6945,12 @@ packages:
|
||||
engines: {node: '>=12.20'}
|
||||
dev: false
|
||||
|
||||
/zod@3.21.1:
|
||||
resolution: {integrity: sha512-+dTu2m6gmCbO9Ahm4ZBDapx2O6ZY9QSPXst2WXjcznPMwf2YNpn3RevLx4KkZp1OPW/ouFcoBtBzFz/LeY69oA==}
|
||||
/zod@3.22.4:
|
||||
resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
|
||||
dev: false
|
||||
|
||||
/zustand@4.4.4(@types/react@18.2.31)(react@18.2.0):
|
||||
resolution: {integrity: sha512-5UTUIAiHMNf5+mFp7/AnzJXS7+XxktULFN0+D1sCiZWyX7ZG+AQpqs2qpYrynRij4QvoDdCD+U+bmg/cG3Ucxw==}
|
||||
/zustand@4.4.6(@types/react@18.2.37)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Rb16eW55gqL4W2XZpJh0fnrATxYEG3Apl2gfHTyDSE965x/zxslTikpNch0JgNjJA9zK6gEFW8Fl6d1rTZaqgg==}
|
||||
engines: {node: '>=12.7.0'}
|
||||
peerDependencies:
|
||||
'@types/react': '>=16.8'
|
||||
@@ -6835,7 +6964,7 @@ packages:
|
||||
react:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.2.31
|
||||
'@types/react': 18.2.37
|
||||
react: 18.2.0
|
||||
use-sync-external-store: 1.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
3
public/images/hackernews.svg
Normal file
3
public/images/hackernews.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 32 32">
|
||||
<path fill="#94a3b8" d="M5 5v22h22V5zm2 2h18v18H7zm4.5 4l3.5 6v5h2v-5l3.5-6h-2L16 15.281L13.5 11z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 203 B |
1
public/images/reddit.svg
Normal file
1
public/images/reddit.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 1024 1024"><path fill="#94a3b8" d="M288 568a56 56 0 1 0 112 0a56 56 0 1 0-112 0zm338.7 119.7c-23.1 18.2-68.9 37.8-114.7 37.8s-91.6-19.6-114.7-37.8c-14.4-11.3-35.3-8.9-46.7 5.5s-8.9 35.3 5.5 46.7C396.3 771.6 457.5 792 512 792s115.7-20.4 155.9-52.1a33.25 33.25 0 1 0-41.2-52.2zM960 456c0-61.9-50.1-112-112-112c-42.1 0-78.7 23.2-97.9 57.6c-57.6-31.5-127.7-51.8-204.1-56.5L612.9 195l127.9 36.9c11.5 32.6 42.6 56.1 79.2 56.1c46.4 0 84-37.6 84-84s-37.6-84-84-84c-32 0-59.8 17.9-74 44.2L603.5 123a33.2 33.2 0 0 0-39.6 18.4l-90.8 203.9c-74.5 5.2-142.9 25.4-199.2 56.2A111.94 111.94 0 0 0 176 344c-61.9 0-112 50.1-112 112c0 45.8 27.5 85.1 66.8 102.5c-7.1 21-10.8 43-10.8 65.5c0 154.6 175.5 280 392 280s392-125.4 392-280c0-22.6-3.8-44.5-10.8-65.5C932.5 541.1 960 501.8 960 456zM820 172.5a31.5 31.5 0 1 1 0 63a31.5 31.5 0 0 1 0-63zM120 456c0-30.9 25.1-56 56-56a56 56 0 0 1 50.6 32.1c-29.3 22.2-53.5 47.8-71.5 75.9a56.23 56.23 0 0 1-35.1-52zm392 381.5c-179.8 0-325.5-95.6-325.5-213.5S332.2 410.5 512 410.5S837.5 506.1 837.5 624S691.8 837.5 512 837.5zM868.8 508c-17.9-28.1-42.2-53.7-71.5-75.9c9-18.9 28.3-32.1 50.6-32.1c30.9 0 56 25.1 56 56c.1 23.5-14.5 43.7-35.1 52zM624 568a56 56 0 1 0 112 0a56 56 0 1 0-112 0z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/pdfs/roadmaps/server-side-game-developer.pdf
Normal file
BIN
public/pdfs/roadmaps/server-side-game-developer.pdf
Normal file
Binary file not shown.
BIN
public/pdfs/roadmaps/technical-writer.pdf
Normal file
BIN
public/pdfs/roadmaps/technical-writer.pdf
Normal file
Binary file not shown.
BIN
public/roadmaps/technical-writer.png
Normal file
BIN
public/roadmaps/technical-writer.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 522 KiB |
@@ -39,7 +39,7 @@ Here is the list of available roadmaps with more being actively worked upon.
|
||||
- [QA Roadmap](https://roadmap.sh/qa)
|
||||
- [Python Roadmap](https://roadmap.sh/python)
|
||||
- [Software Architect Roadmap](https://roadmap.sh/software-architect)
|
||||
- [Game Developer Roadmap](https://roadmap.sh/game-developer)
|
||||
- [Game Developer Roadmap](https://roadmap.sh/game-developer) / [Server Side Game Developer](https://roadmap.sh/server-side-game-developer)
|
||||
- [Software Design and Architecture Roadmap](https://roadmap.sh/software-design-architecture)
|
||||
- [JavaScript Roadmap](https://roadmap.sh/javascript)
|
||||
- [TypeScript Roadmap](https://roadmap.sh/typescript)
|
||||
@@ -67,6 +67,7 @@ Here is the list of available roadmaps with more being actively worked upon.
|
||||
- [UX Design Roadmap](https://roadmap.sh/ux-design)
|
||||
- [Docker Roadmap](https://roadmap.sh/docker)
|
||||
- [Prompt Engineering Roadmap](https://roadmap.sh/prompt-engineering)
|
||||
- [Technical Writer Roadmap](https://roadmap.sh/technical-writer)
|
||||
|
||||
There are also interactive best practices:
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-#!/usr/bin/env bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import RoadmapIcon from '../../icons/roadmap.svg';
|
||||
import { RoadmapIcon } from "../ReactIcons/RoadmapIcon";
|
||||
|
||||
export function EmptyActivity() {
|
||||
return (
|
||||
<div className="rounded-md">
|
||||
<div className="flex flex-col items-center p-7 text-center">
|
||||
<img
|
||||
alt="no roadmaps"
|
||||
src={RoadmapIcon.src}
|
||||
className="mb-2 w-[60px] h-[60px] sm:h-[120px] sm:w-[120px] opacity-10"
|
||||
/>
|
||||
<RoadmapIcon className="mb-2 w-[60px] h-[60px] sm:h-[120px] sm:w-[120px] opacity-10" />
|
||||
|
||||
<h2 className="text-lg sm:text-xl font-bold">No Progress</h2>
|
||||
<p className="my-1 sm:my-2 max-w-[400px] text-gray-500 text-sm sm:text-base">
|
||||
Progress will appear here as you start tracking your{' '}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import GitHubIcon from '../../icons/github.svg';
|
||||
import SpinnerIcon from '../../icons/spinner.svg';
|
||||
import { GitHubIcon } from '../ReactIcons/GitHubIcon.tsx';
|
||||
import Cookies from 'js-cookie';
|
||||
import { TOKEN_COOKIE_NAME } from '../../lib/jwt';
|
||||
import { httpGet } from '../../lib/http';
|
||||
import { Spinner } from '../ReactIcons/Spinner.tsx';
|
||||
|
||||
type GitHubButtonProps = {};
|
||||
|
||||
@@ -13,7 +13,6 @@ const GITHUB_LAST_PAGE = 'githubLastPage';
|
||||
export function GitHubButton(props: GitHubButtonProps) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const icon = isLoading ? SpinnerIcon : GitHubIcon;
|
||||
|
||||
useEffect(() => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
@@ -29,7 +28,7 @@ export function GitHubButton(props: GitHubButtonProps) {
|
||||
httpGet<{ token: string }>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-github-callback${
|
||||
window.location.search
|
||||
}`
|
||||
}`,
|
||||
)
|
||||
.then(({ response, error }) => {
|
||||
if (!response?.token) {
|
||||
@@ -81,12 +80,12 @@ export function GitHubButton(props: GitHubButtonProps) {
|
||||
setIsLoading(true);
|
||||
|
||||
const { response, error } = await httpGet<{ loginUrl: string }>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-github-login`
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-github-login`,
|
||||
);
|
||||
|
||||
if (error || !response?.loginUrl) {
|
||||
setError(
|
||||
error?.message || 'Something went wrong. Please try again later.'
|
||||
error?.message || 'Something went wrong. Please try again later.',
|
||||
);
|
||||
|
||||
setIsLoading(false);
|
||||
@@ -97,7 +96,7 @@ export function GitHubButton(props: GitHubButtonProps) {
|
||||
// the user was on before they clicked the social login button
|
||||
if (!['/login', '/signup'].includes(window.location.pathname)) {
|
||||
const pagePath = ['/respond-invite', '/befriend'].includes(
|
||||
window.location.pathname
|
||||
window.location.pathname,
|
||||
)
|
||||
? window.location.pathname + window.location.search
|
||||
: window.location.pathname;
|
||||
@@ -116,11 +115,11 @@ export function GitHubButton(props: GitHubButtonProps) {
|
||||
disabled={isLoading}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<img
|
||||
src={icon.src}
|
||||
alt="GitHub"
|
||||
className={`h-[18px] w-[18px] ${isLoading ? 'animate-spin' : ''}`}
|
||||
/>
|
||||
{isLoading ? (
|
||||
<Spinner className={'h-[18px] w-[18px]'} isDualRing={false} />
|
||||
) : (
|
||||
<GitHubIcon className={'h-[18px] w-[18px]'} />
|
||||
)}
|
||||
Continue with GitHub
|
||||
</button>
|
||||
{error && (
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import Cookies from 'js-cookie';
|
||||
import GoogleIcon from '../../icons/google.svg';
|
||||
import SpinnerIcon from '../../icons/spinner.svg';
|
||||
import { TOKEN_COOKIE_NAME } from '../../lib/jwt';
|
||||
import { httpGet } from '../../lib/http';
|
||||
import { Spinner } from '../ReactIcons/Spinner.tsx';
|
||||
import { GoogleIcon } from '../ReactIcons/GoogleIcon.tsx';
|
||||
|
||||
type GoogleButtonProps = {};
|
||||
|
||||
@@ -13,7 +13,6 @@ const GOOGLE_LAST_PAGE = 'googleLastPage';
|
||||
export function GoogleButton(props: GoogleButtonProps) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const icon = isLoading ? SpinnerIcon : GoogleIcon;
|
||||
|
||||
useEffect(() => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
@@ -29,7 +28,7 @@ export function GoogleButton(props: GoogleButtonProps) {
|
||||
httpGet<{ token: string }>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-google-callback${
|
||||
window.location.search
|
||||
}`
|
||||
}`,
|
||||
)
|
||||
.then(({ response, error }) => {
|
||||
if (!response?.token) {
|
||||
@@ -79,7 +78,7 @@ export function GoogleButton(props: GoogleButtonProps) {
|
||||
const handleClick = () => {
|
||||
setIsLoading(true);
|
||||
httpGet<{ loginUrl: string }>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-google-login`
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-google-login`,
|
||||
)
|
||||
.then(({ response, error }) => {
|
||||
if (!response?.loginUrl) {
|
||||
@@ -93,7 +92,7 @@ export function GoogleButton(props: GoogleButtonProps) {
|
||||
// the user was on before they clicked the social login button
|
||||
if (!['/login', '/signup'].includes(window.location.pathname)) {
|
||||
const pagePath = ['/respond-invite', '/befriend'].includes(
|
||||
window.location.pathname
|
||||
window.location.pathname,
|
||||
)
|
||||
? window.location.pathname + window.location.search
|
||||
: window.location.pathname;
|
||||
@@ -117,11 +116,11 @@ export function GoogleButton(props: GoogleButtonProps) {
|
||||
disabled={isLoading}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<img
|
||||
src={icon.src}
|
||||
alt="Google"
|
||||
className={`h-[18px] w-[18px] ${isLoading ? 'animate-spin' : ''}`}
|
||||
/>
|
||||
{isLoading ? (
|
||||
<Spinner className={'h-[18px] w-[18px]'} isDualRing={false} />
|
||||
) : (
|
||||
<GoogleIcon className={'h-[18px] w-[18px]'} />
|
||||
)}
|
||||
Continue with Google
|
||||
</button>
|
||||
{error && (
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import Cookies from 'js-cookie';
|
||||
import LinkedIn from '../../icons/linkedin.svg';
|
||||
import SpinnerIcon from '../../icons/spinner.svg';
|
||||
import { TOKEN_COOKIE_NAME } from '../../lib/jwt';
|
||||
import { httpGet } from '../../lib/http';
|
||||
import { Spinner } from '../ReactIcons/Spinner.tsx';
|
||||
import { LinkedInIcon } from '../ReactIcons/LinkedInIcon.tsx';
|
||||
|
||||
type LinkedInButtonProps = {};
|
||||
|
||||
@@ -13,7 +13,6 @@ const LINKEDIN_LAST_PAGE = 'linkedInLastPage';
|
||||
export function LinkedInButton(props: LinkedInButtonProps) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const icon = isLoading ? SpinnerIcon : LinkedIn;
|
||||
|
||||
useEffect(() => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
@@ -29,7 +28,7 @@ export function LinkedInButton(props: LinkedInButtonProps) {
|
||||
httpGet<{ token: string }>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-linkedin-callback${
|
||||
window.location.search
|
||||
}`
|
||||
}`,
|
||||
)
|
||||
.then(({ response, error }) => {
|
||||
if (!response?.token) {
|
||||
@@ -79,7 +78,7 @@ export function LinkedInButton(props: LinkedInButtonProps) {
|
||||
const handleClick = () => {
|
||||
setIsLoading(true);
|
||||
httpGet<{ loginUrl: string }>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-linkedin-login`
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-linkedin-login`,
|
||||
)
|
||||
.then(({ response, error }) => {
|
||||
if (!response?.loginUrl) {
|
||||
@@ -93,7 +92,7 @@ export function LinkedInButton(props: LinkedInButtonProps) {
|
||||
// the user was on before they clicked the social login button
|
||||
if (!['/login', '/signup'].includes(window.location.pathname)) {
|
||||
const pagePath = ['/respond-invite', '/befriend'].includes(
|
||||
window.location.pathname
|
||||
window.location.pathname,
|
||||
)
|
||||
? window.location.pathname + window.location.search
|
||||
: window.location.pathname;
|
||||
@@ -117,11 +116,11 @@ export function LinkedInButton(props: LinkedInButtonProps) {
|
||||
disabled={isLoading}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<img
|
||||
src={icon.src}
|
||||
alt="Google"
|
||||
className={`h-[18px] w-[18px] ${isLoading ? 'animate-spin' : ''}`}
|
||||
/>
|
||||
{isLoading ? (
|
||||
<Spinner className={'h-[18px] w-[18px]'} isDualRing={false} />
|
||||
) : (
|
||||
<LinkedInIcon className={'h-[18px] w-[18px]'} />
|
||||
)}
|
||||
Continue with LinkedIn
|
||||
</button>
|
||||
{error && (
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import Cookies from 'js-cookie';
|
||||
import { httpPost } from '../../lib/http';
|
||||
import ErrorIcon from '../../icons/error.svg';
|
||||
import SpinnerIcon from '../../icons/spinner.svg';
|
||||
import { TOKEN_COOKIE_NAME } from '../../lib/jwt';
|
||||
import { Spinner } from '../ReactIcons/Spinner';
|
||||
import { ErrorIcon2 } from '../ReactIcons/ErrorIcon2';
|
||||
|
||||
export function TriggerVerifyAccount() {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
@@ -16,7 +16,7 @@ export function TriggerVerifyAccount() {
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-verify-account`,
|
||||
{
|
||||
code,
|
||||
}
|
||||
},
|
||||
)
|
||||
.then(({ response, error }) => {
|
||||
if (!response?.token) {
|
||||
@@ -55,20 +55,8 @@ export function TriggerVerifyAccount() {
|
||||
return (
|
||||
<div className="mx-auto flex max-w-md flex-col items-center pt-0 sm:pt-12">
|
||||
<div className="mx-auto max-w-md text-center">
|
||||
{isLoading && (
|
||||
<img
|
||||
alt={'Please wait.'}
|
||||
src={SpinnerIcon.src}
|
||||
className={'mx-auto h-16 w-16 animate-spin'}
|
||||
/>
|
||||
)}
|
||||
{error && (
|
||||
<img
|
||||
alt={'Please wait.'}
|
||||
src={ErrorIcon.src}
|
||||
className={'mx-auto h-16 w-16'}
|
||||
/>
|
||||
)}
|
||||
{isLoading && <Spinner className="mx-auto h-16 w-16" />}
|
||||
{error && <ErrorIcon2 className="mx-auto h-16 w-16" />}
|
||||
<h2 className="mb-1 mt-4 text-center text-xl font-semibold sm:mb-3 sm:mt-4 sm:text-2xl">
|
||||
Verifying your account
|
||||
</h2>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import VerifyLetterIcon from '../../icons/verify-letter.svg';
|
||||
import { httpPost } from '../../lib/http';
|
||||
import { VerifyLetterIcon } from '../ReactIcons/VerifyLetterIcon';
|
||||
|
||||
export function VerificationEmailMessage() {
|
||||
const [email, setEmail] = useState('..');
|
||||
@@ -37,11 +37,7 @@ export function VerificationEmailMessage() {
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-md text-center">
|
||||
<img
|
||||
alt="Verify Email"
|
||||
src={VerifyLetterIcon.src}
|
||||
className="mx-auto mb-4 h-20 w-40 sm:h-40"
|
||||
/>
|
||||
<VerifyLetterIcon className="mx-auto mb-4 h-20 w-40 sm:h-40" />
|
||||
<h2 className="my-2 text-center text-xl font-semibold sm:my-5 sm:text-2xl">
|
||||
Verify your email address
|
||||
</h2>
|
||||
|
||||
@@ -1,35 +1,47 @@
|
||||
import { Fragment, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
Fragment,
|
||||
type ReactElement,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useKeydown } from '../../hooks/use-keydown';
|
||||
import { useOutsideClick } from '../../hooks/use-outside-click';
|
||||
import BestPracticesIcon from '../../icons/best-practices.svg';
|
||||
import ClipboardIcon from '../../icons/clipboard.svg';
|
||||
import GuideIcon from '../../icons/guide.svg';
|
||||
import HomeIcon from '../../icons/home.svg';
|
||||
import RoadmapIcon from '../../icons/roadmap.svg';
|
||||
import UserIcon from '../../icons/user.svg';
|
||||
import GroupIcon from '../../icons/group.svg';
|
||||
import VideoIcon from '../../icons/video.svg';
|
||||
import { httpGet } from '../../lib/http';
|
||||
import { isLoggedIn } from '../../lib/jwt';
|
||||
import { BestPracticesIcon } from '../ReactIcons/BestPracticesIcon.tsx';
|
||||
import { UserIcon } from '../ReactIcons/UserIcon.tsx';
|
||||
import { GroupIcon } from '../ReactIcons/GroupIcon.tsx';
|
||||
import { RoadmapIcon } from '../ReactIcons/RoadmapIcon.tsx';
|
||||
import { ClipboardIcon } from '../ReactIcons/ClipboardIcon.tsx';
|
||||
import { GuideIcon } from '../ReactIcons/GuideIcon.tsx';
|
||||
import { HomeIcon } from '../ReactIcons/HomeIcon.tsx';
|
||||
import { VideoIcon } from '../ReactIcons/VideoIcon.tsx';
|
||||
|
||||
export type PageType = {
|
||||
id: string;
|
||||
url: string;
|
||||
title: string;
|
||||
group: string;
|
||||
icon?: string;
|
||||
icon?: ReactElement;
|
||||
isProtected?: boolean;
|
||||
metadata?: Record<string, any>;
|
||||
};
|
||||
|
||||
const defaultPages: PageType[] = [
|
||||
{ id: 'home', url: '/', title: 'Home', group: 'Pages', icon: HomeIcon.src },
|
||||
{
|
||||
id: 'home',
|
||||
url: '/',
|
||||
title: 'Home',
|
||||
group: 'Pages',
|
||||
icon: <HomeIcon className="mr-2 h-4 w-4 stroke-2" />,
|
||||
},
|
||||
{
|
||||
id: 'account',
|
||||
url: '/account',
|
||||
title: 'Account',
|
||||
group: 'Pages',
|
||||
icon: UserIcon.src,
|
||||
icon: <UserIcon className="mr-2 h-4 w-4 stroke-2" />,
|
||||
isProtected: true,
|
||||
},
|
||||
{
|
||||
@@ -37,7 +49,7 @@ const defaultPages: PageType[] = [
|
||||
url: '/team',
|
||||
title: 'Teams',
|
||||
group: 'Pages',
|
||||
icon: GroupIcon.src,
|
||||
icon: <GroupIcon className="mr-2 h-4 w-4 stroke-2" />,
|
||||
isProtected: true,
|
||||
},
|
||||
{
|
||||
@@ -45,7 +57,7 @@ const defaultPages: PageType[] = [
|
||||
url: '/account/friends',
|
||||
title: 'Friends',
|
||||
group: 'Pages',
|
||||
icon: GroupIcon.src,
|
||||
icon: <GroupIcon className="mr-2 h-4 w-4 stroke-2" />,
|
||||
isProtected: true,
|
||||
},
|
||||
{
|
||||
@@ -53,14 +65,14 @@ const defaultPages: PageType[] = [
|
||||
url: '/roadmaps',
|
||||
title: 'Roadmaps',
|
||||
group: 'Pages',
|
||||
icon: RoadmapIcon.src,
|
||||
icon: <RoadmapIcon className="mr-2 h-4 w-4 stroke-2" />,
|
||||
},
|
||||
{
|
||||
id: 'account-roadmaps',
|
||||
url: '/account/roadmaps',
|
||||
title: 'Custom Roadmaps',
|
||||
group: 'Pages',
|
||||
icon: RoadmapIcon.src,
|
||||
icon: <RoadmapIcon className="mr-2 h-4 w-4 stroke-2" />,
|
||||
isProtected: true,
|
||||
},
|
||||
{
|
||||
@@ -68,28 +80,28 @@ const defaultPages: PageType[] = [
|
||||
url: '/best-practices',
|
||||
title: 'Best Practices',
|
||||
group: 'Pages',
|
||||
icon: BestPracticesIcon.src,
|
||||
icon: <BestPracticesIcon className="mr-2 h-4 w-4 stroke-2" />,
|
||||
},
|
||||
{
|
||||
id: 'questions',
|
||||
url: '/questions',
|
||||
title: 'Questions',
|
||||
group: 'Pages',
|
||||
icon: ClipboardIcon.src,
|
||||
icon: <ClipboardIcon className="mr-2 h-4 w-4 stroke-2" />,
|
||||
},
|
||||
{
|
||||
id: 'guides',
|
||||
url: '/guides',
|
||||
title: 'Guides',
|
||||
group: 'Pages',
|
||||
icon: GuideIcon.src,
|
||||
icon: <GuideIcon className="mr-2 h-4 w-4 stroke-2" />,
|
||||
},
|
||||
{
|
||||
id: 'videos',
|
||||
url: '/videos',
|
||||
title: 'Videos',
|
||||
group: 'Pages',
|
||||
icon: VideoIcon.src,
|
||||
icon: <VideoIcon className="mr-2 h-4 w-4 stroke-2" />,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -199,7 +211,7 @@ export function CommandMenu() {
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
const canGoPrev = activeCounter > 0;
|
||||
setActiveCounter(
|
||||
canGoPrev ? activeCounter - 1 : searchResults.length - 1
|
||||
canGoPrev ? activeCounter - 1 : searchResults.length - 1,
|
||||
);
|
||||
} else if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
@@ -242,13 +254,7 @@ export function CommandMenu() {
|
||||
{!page.icon && (
|
||||
<span className="mr-2 text-gray-400">{page.group}</span>
|
||||
)}
|
||||
{page.icon && (
|
||||
<img
|
||||
alt={page.title}
|
||||
src={page.icon}
|
||||
className="mr-2 h-4 w-4"
|
||||
/>
|
||||
)}
|
||||
{page.icon && page.icon}
|
||||
{page.title}
|
||||
</a>
|
||||
</Fragment>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ChevronDownIcon from '../../icons/chevron-down.svg';
|
||||
import { ChevronDownIcon } from '../ReactIcons/ChevronDownIcon';
|
||||
|
||||
type NotDropdownProps = {
|
||||
onClick: () => void;
|
||||
@@ -37,11 +37,7 @@ export function NotDropdown(props: NotDropdownProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<img
|
||||
alt={singularName}
|
||||
src={ChevronDownIcon.src}
|
||||
className={'relative top-[1px] h-[17px] w-[17px] opacity-40'}
|
||||
/>
|
||||
<ChevronDownIcon className="relative top-[1px] h-[17px] w-[17px] opacity-40" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ import { useKeydown } from '../../hooks/use-keydown';
|
||||
import { useOutsideClick } from '../../hooks/use-outside-click';
|
||||
import type { PageType } from '../CommandMenu/CommandMenu';
|
||||
import type { TeamResourceConfig } from './RoadmapSelector';
|
||||
import CloseIcon from '../../icons/close.svg';
|
||||
import { SelectRoadmapModalItem } from './SelectRoadmapModalItem';
|
||||
import { XIcon } from 'lucide-react';
|
||||
|
||||
export type SelectRoadmapModalProps = {
|
||||
teamId: string;
|
||||
@@ -60,11 +60,11 @@ export function SelectRoadmapModal(props: SelectRoadmapModalProps) {
|
||||
setSearchResults(searchResults);
|
||||
}, [searchText, allRoadmaps]);
|
||||
|
||||
const roleBasedRoadmaps = searchResults.filter((roadmap) =>
|
||||
roadmap?.metadata?.tags?.includes('role-roadmap')
|
||||
const roleBasedRoadmaps = searchResults.filter(
|
||||
(roadmap) => roadmap?.metadata?.tags?.includes('role-roadmap'),
|
||||
);
|
||||
const skillBasedRoadmaps = searchResults.filter((roadmap) =>
|
||||
roadmap?.metadata?.tags?.includes('skill-roadmap')
|
||||
const skillBasedRoadmaps = searchResults.filter(
|
||||
(roadmap) => roadmap?.metadata?.tags?.includes('skill-roadmap'),
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -79,7 +79,7 @@ export function SelectRoadmapModal(props: SelectRoadmapModalProps) {
|
||||
className="popup-close absolute right-2.5 top-3 ml-auto inline-flex items-center rounded-lg bg-transparent p-1.5 text-sm text-gray-400 hover:bg-gray-100 hover:text-gray-900"
|
||||
onClick={onClose}
|
||||
>
|
||||
<img alt={'close'} src={CloseIcon.src} className="h-4 w-4" />
|
||||
<XIcon className="h-4 w-4" />
|
||||
<span className="sr-only">Close modal</span>
|
||||
</button>
|
||||
<input
|
||||
@@ -101,7 +101,7 @@ export function SelectRoadmapModal(props: SelectRoadmapModalProps) {
|
||||
<div className="mb-5 flex flex-wrap items-center gap-2">
|
||||
{roleBasedRoadmaps.map((roadmap) => {
|
||||
const isSelected = !!teamResourceConfig?.find(
|
||||
(r) => r.resourceId === roadmap.id
|
||||
(r) => r.resourceId === roadmap.id,
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -127,7 +127,7 @@ export function SelectRoadmapModal(props: SelectRoadmapModalProps) {
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{skillBasedRoadmaps.map((roadmap) => {
|
||||
const isSelected = !!teamResourceConfig.find(
|
||||
(r) => r.resourceId === roadmap.id
|
||||
(r) => r.resourceId === roadmap.id,
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import BuildingIcon from '../../icons/building.svg';
|
||||
import UsersIcon from '../../icons/users.svg';
|
||||
import type { TeamDocument } from './CreateTeamForm';
|
||||
import { httpPut } from '../../lib/http';
|
||||
import { useState } from 'react';
|
||||
import { NextButton } from './NextButton';
|
||||
import { BuildingIcon } from '../ReactIcons/BuildingIcon.tsx';
|
||||
import { UsersIcon } from '../ReactIcons/UsersIcon.tsx';
|
||||
|
||||
export const validTeamTypes = [
|
||||
{
|
||||
value: 'company',
|
||||
label: 'Company',
|
||||
icon: BuildingIcon.src,
|
||||
icon: BuildingIcon,
|
||||
description:
|
||||
'Track the skills and learning progress of the tech team at your company',
|
||||
},
|
||||
{
|
||||
value: 'study_group',
|
||||
label: 'Study Group',
|
||||
icon: UsersIcon.src,
|
||||
icon: UsersIcon,
|
||||
description:
|
||||
'Invite your friends or course-mates and track your learning progress together',
|
||||
},
|
||||
@@ -56,7 +56,7 @@ export function Step0(props: Step0Props) {
|
||||
teamSize: team.teamSize,
|
||||
linkedInUrl: team?.links?.linkedIn || undefined,
|
||||
}),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (error || !response) {
|
||||
@@ -76,21 +76,20 @@ export function Step0(props: Step0Props) {
|
||||
{validTeamTypes.map((validTeamType) => (
|
||||
<button
|
||||
key={validTeamType.value}
|
||||
className={`flex flex-grow flex-col items-center rounded-lg border px-5 pt-12 pb-10 ${
|
||||
className={`flex flex-grow flex-col items-center rounded-lg border px-5 pb-10 pt-12 ${
|
||||
validTeamType.value == selectedTeamType
|
||||
? 'border-gray-400 bg-gray-100'
|
||||
: 'border-gray-300 hover:border-gray-400 hover:bg-gray-50'
|
||||
}`}
|
||||
onClick={() => setSelectedTeamType(validTeamType.value)}
|
||||
>
|
||||
<img
|
||||
key={validTeamType.value}
|
||||
alt={validTeamType.label}
|
||||
src={validTeamType.icon}
|
||||
className={`mb-3 h-12 w-12 opacity-10 ${
|
||||
validTeamType.value === selectedTeamType ? 'opacity-100' : ''
|
||||
}`}
|
||||
/>
|
||||
{
|
||||
<validTeamType.icon
|
||||
className={`mb-3 h-12 w-12 opacity-10 ${
|
||||
validTeamType.value === selectedTeamType ? 'opacity-100' : ''
|
||||
}`}
|
||||
/>
|
||||
}
|
||||
<span className="mb-2 block text-2xl font-bold">
|
||||
{validTeamType.label}
|
||||
</span>
|
||||
|
||||
@@ -13,6 +13,7 @@ import type { Node } from 'reactflow';
|
||||
import { useCallback, type MouseEvent, useMemo, useState, useRef } from 'react';
|
||||
import { EmptyRoadmap } from './EmptyRoadmap';
|
||||
import { cn } from '../../lib/classname';
|
||||
import { totalRoadmapNodes } from '../../stores/roadmap.ts';
|
||||
|
||||
type FlowRoadmapRendererProps = {
|
||||
roadmap: RoadmapDocument;
|
||||
@@ -138,6 +139,12 @@ export function FlowRoadmapRenderer(props: FlowRoadmapRendererProps) {
|
||||
)}
|
||||
onRendered={() => {
|
||||
renderResourceProgress('roadmap', roadmapId).then(() => {
|
||||
totalRoadmapNodes.set(
|
||||
roadmap?.nodes?.filter((node) => {
|
||||
return ['topic', 'subtopic'].includes(node.type);
|
||||
}).length || 0,
|
||||
);
|
||||
|
||||
if (roadmap?.nodes?.length === 0) {
|
||||
setHideRenderer(true);
|
||||
editorWrapperRef?.current?.classList.add('hidden');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import MoreIcon from '../../icons/more-vertical.svg';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useOutsideClick } from '../../hooks/use-outside-click';
|
||||
import { Lock, MoreVertical, Shapes, Trash2 } from 'lucide-react';
|
||||
import { MoreVerticalIcon } from '../ReactIcons/MoreVerticalIcon.tsx';
|
||||
|
||||
type PersonalRoadmapActionDropdownProps = {
|
||||
onDelete?: () => void;
|
||||
@@ -9,7 +9,9 @@ type PersonalRoadmapActionDropdownProps = {
|
||||
onUpdateSharing?: () => void;
|
||||
};
|
||||
|
||||
export function PersonalRoadmapActionDropdown(props: PersonalRoadmapActionDropdownProps) {
|
||||
export function PersonalRoadmapActionDropdown(
|
||||
props: PersonalRoadmapActionDropdownProps,
|
||||
) {
|
||||
const { onDelete, onUpdateSharing, onCustomize } = props;
|
||||
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
@@ -26,7 +28,7 @@ export function PersonalRoadmapActionDropdown(props: PersonalRoadmapActionDropdo
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="hidden items-center opacity-60 transition-opacity hover:opacity-100 disabled:cursor-not-allowed disabled:opacity-30 sm:flex"
|
||||
>
|
||||
<img alt="menu" src={MoreIcon.src} className="h-4 w-4" />
|
||||
<MoreVerticalIcon className={'h-4 w-4'} />
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
||||
@@ -14,11 +14,11 @@ import {
|
||||
type AllowedRoadmapVisibility,
|
||||
type RoadmapDocument,
|
||||
} from './CreateRoadmap/CreateRoadmapModal';
|
||||
import RoadmapIcon from '../../icons/roadmap.svg';
|
||||
import { PersonalRoadmapActionDropdown } from './PersonalRoadmapActionDropdown';
|
||||
import type { GetRoadmapListResponse } from './RoadmapListPage';
|
||||
import { useState, type Dispatch, type SetStateAction } from 'react';
|
||||
import { ShareOptionsModal } from '../ShareOptions/ShareOptionsModal';
|
||||
import {RoadmapIcon} from "../ReactIcons/RoadmapIcon.tsx";
|
||||
|
||||
type PersonalRoadmapListType = {
|
||||
roadmaps: GetRoadmapListResponse['personalRoadmaps'];
|
||||
@@ -91,11 +91,8 @@ export function PersonalRoadmapList(props: PersonalRoadmapListType) {
|
||||
if (roadmapList.length === 0) {
|
||||
return (
|
||||
<div className="flex flex-col items-center p-4 py-20">
|
||||
<img
|
||||
alt="roadmap"
|
||||
src={RoadmapIcon.src}
|
||||
className="mb-4 h-24 w-24 opacity-10"
|
||||
/>
|
||||
<RoadmapIcon className="mb-4 h-24 w-24 opacity-10" />
|
||||
|
||||
<h3 className="mb-1 text-2xl font-bold text-gray-900">No roadmaps</h3>
|
||||
<p className="text-base text-gray-500">
|
||||
Create a roadmap to get started
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
import Loader from '../Loader.astro';
|
||||
import './FrameRenderer.css';
|
||||
import { ProgressNudge } from "./ProgressNudge";
|
||||
|
||||
export interface Props {
|
||||
resourceType: 'roadmap' | 'best-practice';
|
||||
@@ -27,4 +28,6 @@ const { resourceId, resourceType, dimensions = null } = Astro.props;
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ProgressNudge resourceId={resourceId} resourceType={resourceType} client:only="react" />
|
||||
|
||||
<script src='./renderer.ts'></script>
|
||||
|
||||
65
src/components/FrameRenderer/ProgressNudge.tsx
Normal file
65
src/components/FrameRenderer/ProgressNudge.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { cn } from '../../lib/classname.ts';
|
||||
import { roadmapProgress, totalRoadmapNodes } from '../../stores/roadmap.ts';
|
||||
import { useStore } from '@nanostores/react';
|
||||
|
||||
type ProgressNudgeProps = {
|
||||
resourceType: 'roadmap' | 'best-practice';
|
||||
resourceId: string;
|
||||
};
|
||||
|
||||
export function ProgressNudge(props: ProgressNudgeProps) {
|
||||
const $totalRoadmapNodes = useStore(totalRoadmapNodes);
|
||||
const $roadmapProgress = useStore(roadmapProgress);
|
||||
|
||||
const done = $roadmapProgress?.done?.length || 0;
|
||||
|
||||
const hasProgress = done > 0;
|
||||
|
||||
if (!$totalRoadmapNodes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
'fixed bottom-5 left-1/2 z-30 hidden -translate-x-1/2 transform animate-fade-slide-up overflow-hidden rounded-full bg-stone-900 px-4 py-2 text-center text-white shadow-2xl transition-all duration-300 sm:block'
|
||||
}
|
||||
>
|
||||
<span
|
||||
className={cn('block', {
|
||||
hidden: hasProgress,
|
||||
})}
|
||||
>
|
||||
<span className="mr-2 text-sm font-semibold uppercase text-yellow-400">
|
||||
Tip
|
||||
</span>
|
||||
<span className="text-sm text-gray-200">
|
||||
Right-click on a topic to mark it as done.{' '}
|
||||
<button
|
||||
data-popup="progress-help"
|
||||
className="cursor-pointer font-semibold text-yellow-500 underline"
|
||||
>
|
||||
Learn more.
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className={cn('relative z-20 block text-sm', {
|
||||
hidden: !hasProgress,
|
||||
})}
|
||||
>
|
||||
<span className="relative -top-[0.45px] mr-2 text-xs font-medium uppercase text-yellow-400">
|
||||
Progress
|
||||
</span>
|
||||
<span>{done}</span> of <span>{$totalRoadmapNodes}</span> Done
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="absolute bottom-0 left-0 top-0 z-10 bg-stone-700"
|
||||
style={{
|
||||
width: `${(done / $totalRoadmapNodes) * 100}%`,
|
||||
}}
|
||||
></span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -7,10 +7,13 @@ import {
|
||||
renderTopicProgress,
|
||||
updateResourceProgress,
|
||||
} from '../../lib/resource-progress';
|
||||
import type { ResourceProgressType, ResourceType } from '../../lib/resource-progress';
|
||||
import type {
|
||||
ResourceProgressType,
|
||||
ResourceType,
|
||||
} from '../../lib/resource-progress';
|
||||
import { pageProgressMessage } from '../../stores/page';
|
||||
import { showLoginPopup } from '../../lib/popup';
|
||||
import {replaceChildren} from "../../lib/dom.ts";
|
||||
import { replaceChildren } from '../../lib/dom.ts';
|
||||
|
||||
export class Renderer {
|
||||
resourceId: string;
|
||||
@@ -95,7 +98,7 @@ export class Renderer {
|
||||
.then(() => {
|
||||
return renderResourceProgress(
|
||||
this.resourceType as ResourceType,
|
||||
this.resourceId
|
||||
this.resourceId,
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -143,7 +146,7 @@ export class Renderer {
|
||||
this.jsonToSvg(
|
||||
this.resourceType === 'roadmap'
|
||||
? `/${this.resourceId}.json`
|
||||
: `/best-practices/${this.resourceId}.json`
|
||||
: `/best-practices/${this.resourceId}.json`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -183,7 +186,7 @@ export class Renderer {
|
||||
resourceType: this.resourceType as ResourceType,
|
||||
topicId,
|
||||
},
|
||||
newStatus
|
||||
newStatus,
|
||||
)
|
||||
.then(() => {
|
||||
renderTopicProgress(topicId, newStatus);
|
||||
@@ -215,9 +218,14 @@ export class Renderer {
|
||||
|
||||
const isCurrentStatusDone = targetGroup.classList.contains('done');
|
||||
const normalizedGroupId = groupId.replace(/^\d+-/, '');
|
||||
|
||||
if (normalizedGroupId.startsWith('ext_link:')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateTopicStatus(
|
||||
normalizedGroupId,
|
||||
!isCurrentStatusDone ? 'done' : 'pending'
|
||||
!isCurrentStatusDone ? 'done' : 'pending',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -243,9 +251,12 @@ export class Renderer {
|
||||
action: `${this.resourceType} / ${this.resourceId}`,
|
||||
label: externalLink,
|
||||
});
|
||||
|
||||
window.open(`https://${externalLink}`);
|
||||
} else {
|
||||
window.location.href = `https://${externalLink}`;
|
||||
}
|
||||
|
||||
window.open(`https://${externalLink}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -265,7 +276,7 @@ export class Renderer {
|
||||
resourceType: this.resourceType,
|
||||
resourceId: this.resourceId,
|
||||
},
|
||||
})
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -280,7 +291,7 @@ export class Renderer {
|
||||
e.preventDefault();
|
||||
this.updateTopicStatus(
|
||||
normalizedGroupId,
|
||||
!isCurrentStatusLearning ? 'learning' : 'pending'
|
||||
!isCurrentStatusLearning ? 'learning' : 'pending',
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -289,7 +300,7 @@ export class Renderer {
|
||||
e.preventDefault();
|
||||
this.updateTopicStatus(
|
||||
normalizedGroupId,
|
||||
!isCurrentStatusSkipped ? 'skipped' : 'pending'
|
||||
!isCurrentStatusSkipped ? 'skipped' : 'pending',
|
||||
);
|
||||
|
||||
return;
|
||||
@@ -302,7 +313,7 @@ export class Renderer {
|
||||
resourceId: this.resourceId,
|
||||
resourceType: this.resourceType,
|
||||
},
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import UserPlusIcon from '../../icons/user-plus.svg';
|
||||
import CopyIcon from '../../icons/copy.svg';
|
||||
import { useCopyText } from '../../hooks/use-copy-text';
|
||||
import { CopyIcon, UserPlus2 } from 'lucide-react';
|
||||
|
||||
type EmptyFriendsProps = {
|
||||
befriendUrl: string;
|
||||
@@ -13,14 +12,12 @@ export function EmptyFriends(props: EmptyFriendsProps) {
|
||||
return (
|
||||
<div className="rounded-md">
|
||||
<div className="mx-auto flex flex-col items-center p-7 text-center">
|
||||
<img
|
||||
alt="no friends"
|
||||
src={UserPlusIcon.src}
|
||||
className="mb-2 h-[60px] w-[60px] opacity-10 sm:h-[120px] sm:w-[120px]"
|
||||
/>
|
||||
<UserPlus2 className="mb-2 h-[60px] w-[60px] opacity-10 sm:h-[120px] sm:w-[120px]" />
|
||||
|
||||
<h2 className="text-lg font-bold sm:text-xl">Invite your Friends</h2>
|
||||
<p className="mb-4 mt-1 max-w-[400px] text-sm leading-relaxed text-gray-500">
|
||||
Share the unique link below with your friends to track their skills and progress.
|
||||
Share the unique link below with your friends to track their skills
|
||||
and progress.
|
||||
</p>
|
||||
|
||||
<div className="flex w-full max-w-[352px] items-center justify-center gap-2 rounded-lg border-2 p-1 text-sm">
|
||||
@@ -44,7 +41,8 @@ export function EmptyFriends(props: EmptyFriendsProps) {
|
||||
copyText(befriendUrl);
|
||||
}}
|
||||
>
|
||||
<img src={CopyIcon.src} className="h-4 w-4" alt="Invite Friends" />
|
||||
<CopyIcon className="mr-1 h-4 w-4" />
|
||||
|
||||
{isCopied ? 'Copied' : 'Copy'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -7,10 +7,10 @@ import type { FriendshipStatus } from '../Befriend';
|
||||
import { useToast } from '../../hooks/use-toast';
|
||||
import { EmptyFriends } from './EmptyFriends';
|
||||
import { FriendProgressItem } from './FriendProgressItem';
|
||||
import UserIcon from '../../icons/user.svg';
|
||||
import { UserProgressModal } from '../UserProgress/UserProgressModal';
|
||||
import { InviteFriendPopup } from './InviteFriendPopup';
|
||||
import { UserCustomProgressModal } from '../UserProgress/UserCustomProgressModal';
|
||||
import { UserIcon } from '../ReactIcons/UserIcon.tsx';
|
||||
|
||||
type FriendResourceProgress = {
|
||||
updatedAt: string;
|
||||
@@ -64,7 +64,7 @@ export function FriendsPage() {
|
||||
|
||||
async function loadFriends() {
|
||||
const { response, error } = await httpGet<ListFriendsResponse>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-list-friends`
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-list-friends`,
|
||||
);
|
||||
|
||||
if (error || !response) {
|
||||
@@ -89,15 +89,15 @@ export function FriendsPage() {
|
||||
const befriendUrl = `${baseUrl}/befriend?u=${user?.id}`;
|
||||
|
||||
const selectedGroupingType = groupingTypes.find(
|
||||
(grouping) => grouping.value === selectedGrouping
|
||||
(grouping) => grouping.value === selectedGrouping,
|
||||
);
|
||||
|
||||
const filteredFriends = friends.filter((friend) =>
|
||||
selectedGroupingType?.statuses.includes(friend.status)
|
||||
const filteredFriends = friends.filter(
|
||||
(friend) => selectedGroupingType?.statuses.includes(friend.status),
|
||||
);
|
||||
|
||||
const receivedRequests = friends.filter(
|
||||
(friend) => friend.status === 'received'
|
||||
(friend) => friend.status === 'received',
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
@@ -203,11 +203,8 @@ export function FriendsPage() {
|
||||
|
||||
{filteredFriends.length === 0 && (
|
||||
<div className="flex flex-col items-center justify-center py-12">
|
||||
<img
|
||||
src={UserIcon.src}
|
||||
alt="Empty Friends"
|
||||
className="mb-3 w-12 opacity-20"
|
||||
/>
|
||||
<UserIcon className="mb-3 w-12 opacity-20" />
|
||||
|
||||
<h2 className="text-lg font-semibold">
|
||||
{selectedGrouping === 'active' && 'No friends yet'}
|
||||
{selectedGrouping === 'sent' && 'No requests sent'}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { MouseEvent } from 'react';
|
||||
import { useRef } from 'react';
|
||||
import { useOutsideClick } from '../../hooks/use-outside-click';
|
||||
import CopyIcon from '../../icons/copy.svg';
|
||||
import { useCopyText } from '../../hooks/use-copy-text';
|
||||
import { CopyIcon } from 'lucide-react';
|
||||
|
||||
type InviteFriendPopupProps = {
|
||||
befriendUrl: string;
|
||||
@@ -54,11 +54,7 @@ export function InviteFriendPopup(props: InviteFriendPopupProps) {
|
||||
copyText(befriendUrl);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={CopyIcon.src}
|
||||
className="h-4 w-4"
|
||||
alt="Invite Friends"
|
||||
/>
|
||||
<CopyIcon className="mr-1 h-4 w-4" />
|
||||
{isCopied ? 'Copied' : 'Copy URL'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,7 @@ import { AccountDropdown } from './AccountDropdown';
|
||||
>Best Practices</a
|
||||
>
|
||||
</li>
|
||||
<li class='hidden lg:inline'>
|
||||
<li class='hidden xl:inline'>
|
||||
<a href='/questions' class='text-gray-400 hover:text-white'>Questions</a
|
||||
>
|
||||
</li>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { httpGet, httpPatch, httpPost } from '../../lib/http';
|
||||
import { httpGet, httpPatch } from '../../lib/http';
|
||||
import { pageProgressMessage } from '../../stores/page';
|
||||
import type { TeamMemberDocument } from '../TeamMembers/TeamMembersPage';
|
||||
import XIcon from '../../icons/close-dark.svg';
|
||||
import AcceptIcon from '../../icons/accept.svg';
|
||||
import { useToast } from '../../hooks/use-toast';
|
||||
import { AcceptIcon } from '../ReactIcons/AcceptIcon.tsx';
|
||||
import { XIcon } from 'lucide-react';
|
||||
|
||||
interface NotificationList extends TeamMemberDocument {
|
||||
name: string;
|
||||
@@ -18,7 +18,7 @@ export function NotificationPage() {
|
||||
|
||||
const lostNotifications = async () => {
|
||||
const { error, response } = await httpGet<NotificationList[]>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-get-invitation-list`
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-get-invitation-list`,
|
||||
);
|
||||
if (error || !response) {
|
||||
toast.error(error?.message || 'Something went wrong');
|
||||
@@ -28,28 +28,37 @@ export function NotificationPage() {
|
||||
setNotifications(response);
|
||||
};
|
||||
|
||||
async function respondInvitation(status: 'accept' | 'reject', inviteId: string) {
|
||||
async function respondInvitation(
|
||||
status: 'accept' | 'reject',
|
||||
inviteId: string,
|
||||
) {
|
||||
setIsLoading(true);
|
||||
setError('');
|
||||
const { response, error } = await httpPatch<{ teamId: string }>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-respond-invite/${inviteId}`, {
|
||||
status
|
||||
});
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-respond-invite/${inviteId}`,
|
||||
{
|
||||
status,
|
||||
},
|
||||
);
|
||||
if (error || !response) {
|
||||
setError(error?.message || 'Something went wrong')
|
||||
setIsLoading(false)
|
||||
setError(error?.message || 'Something went wrong');
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (status === 'accept') {
|
||||
window.location.href = `/team/progress?t=${response.teamId}`;
|
||||
} else {
|
||||
window.dispatchEvent(new CustomEvent('refresh-notification', {
|
||||
detail: {
|
||||
count: notifications.length - 1
|
||||
}
|
||||
}));
|
||||
setNotifications(notifications.filter((notification) => notification._id !== inviteId));
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('refresh-notification', {
|
||||
detail: {
|
||||
count: notifications.length - 1,
|
||||
},
|
||||
}),
|
||||
);
|
||||
setNotifications(
|
||||
notifications.filter((notification) => notification._id !== inviteId),
|
||||
);
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
@@ -66,15 +75,20 @@ export function NotificationPage() {
|
||||
<h2 className="text-3xl font-bold sm:text-4xl">Notification</h2>
|
||||
<p className="mt-2 text-gray-400">Manage your notifications</p>
|
||||
</div>
|
||||
{
|
||||
notifications.length === 0 && (
|
||||
<div className="flex items-center justify-center mt-6">
|
||||
<p className="text-gray-400">
|
||||
No notifications, you can <a href="/team/new" className="text-blue-500 underline hover:no-underline">create a team</a> and invite your friends to join.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{notifications.length === 0 && (
|
||||
<div className="mt-6 flex items-center justify-center">
|
||||
<p className="text-gray-400">
|
||||
No notifications, you can{' '}
|
||||
<a
|
||||
href="/team/new"
|
||||
className="text-blue-500 underline hover:no-underline"
|
||||
>
|
||||
create a team
|
||||
</a>{' '}
|
||||
and invite your friends to join.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-4">
|
||||
{notifications.map((notification) => (
|
||||
<div className="flex items-center justify-between rounded-md border p-2">
|
||||
@@ -86,19 +100,21 @@ export function NotificationPage() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<button type="button"
|
||||
<button
|
||||
type="button"
|
||||
disabled={isLoading}
|
||||
className="inline-flex border p-1 rounded hover:bg-gray-50 disabled:opacity-75"
|
||||
className="inline-flex rounded border p-1 hover:bg-gray-50 disabled:opacity-75"
|
||||
onClick={() => respondInvitation('accept', notification?._id!)}
|
||||
>
|
||||
<img src={AcceptIcon.src} className="h-4 w-4" />
|
||||
<AcceptIcon className="h-4 w-4" />
|
||||
</button>
|
||||
<button type="button"
|
||||
<button
|
||||
type="button"
|
||||
disabled={isLoading}
|
||||
className="inline-flex border p-1 rounded hover:bg-gray-50 disabled:opacity-75"
|
||||
className="inline-flex rounded border p-1 hover:bg-gray-50 disabled:opacity-75"
|
||||
onClick={() => respondInvitation('reject', notification?._id!)}
|
||||
>
|
||||
<img alt={'Close'} src={XIcon.src} className="h-4 w-4" />
|
||||
<XIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useStore } from '@nanostores/react';
|
||||
import SpinnerIcon from '../icons/spinner.svg';
|
||||
import { pageProgressMessage } from '../stores/page';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Spinner } from './ReactIcons/Spinner';
|
||||
|
||||
export interface Props {
|
||||
initialMessage: string;
|
||||
@@ -30,10 +30,10 @@ export function PageProgress(props: Props) {
|
||||
{/* Tailwind based spinner for full page */}
|
||||
<div className="fixed left-0 top-0 z-50 flex h-full w-full items-center justify-center bg-white bg-opacity-75">
|
||||
<div className="flex items-center justify-center rounded-md border bg-white px-4 py-2 ">
|
||||
<img
|
||||
src={SpinnerIcon.src}
|
||||
alt="Loading"
|
||||
className="h-4 w-4 animate-spin fill-blue-600 text-gray-200 sm:h-4 sm:w-4"
|
||||
<Spinner
|
||||
className="h-4 w-4 sm:h-4 sm:w-4"
|
||||
outerFill="#e5e7eb"
|
||||
innerFill="#2563eb"
|
||||
/>
|
||||
<h1 className="ml-2">
|
||||
{message}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import CloseIcon from '../icons/close.svg';
|
||||
import { httpGet } from '../lib/http';
|
||||
import { sponsorHidden } from '../stores/page';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
export type PageSponsorType = {
|
||||
company: string;
|
||||
@@ -46,7 +46,7 @@ export function PageSponsor(props: PageSponsorProps) {
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-get-sponsor`,
|
||||
{
|
||||
href: window.location.pathname,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (error) {
|
||||
@@ -101,7 +101,7 @@ export function PageSponsor(props: PageSponsorProps) {
|
||||
sponsorHidden.set(true);
|
||||
}}
|
||||
>
|
||||
<img alt="Close" className="h-4 w-4" src={CloseIcon.src} />
|
||||
<X className="h-4 w-4" />
|
||||
</span>
|
||||
<img
|
||||
src={imageUrl}
|
||||
|
||||
24
src/components/ReactIcons/AcceptIcon.tsx
Normal file
24
src/components/ReactIcons/AcceptIcon.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
type AcceptIconProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function AcceptIcon(props: AcceptIconProps) {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="2"
|
||||
stroke="#000"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M4.5 12.75l6 6 9-13.5"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
28
src/components/ReactIcons/BestPracticesIcon.tsx
Normal file
28
src/components/ReactIcons/BestPracticesIcon.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
type BestPracticesIconProps = {
|
||||
className?: string;
|
||||
};
|
||||
export function BestPracticesIcon(props: BestPracticesIconProps) {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<line x1="10" x2="21" y1="6" y2="6"></line>
|
||||
<line x1="10" x2="21" y1="12" y2="12"></line>
|
||||
<line x1="10" x2="21" y1="18" y2="18"></line>
|
||||
<polyline points="3 6 4 7 6 5"></polyline>
|
||||
<polyline points="3 12 4 13 6 11"></polyline>
|
||||
<polyline points="3 18 4 19 6 17"></polyline>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
29
src/components/ReactIcons/BuildingIcon.tsx
Normal file
29
src/components/ReactIcons/BuildingIcon.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
type BuildingIconProps = {
|
||||
className?: string;
|
||||
};
|
||||
export function BuildingIcon(props: BuildingIconProps) {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<path d="M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z"></path>
|
||||
<path d="M6 12H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h2"></path>
|
||||
<path d="M18 9h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-2"></path>
|
||||
<path d="M10 6h4"></path>
|
||||
<path d="M10 10h4"></path>
|
||||
<path d="M10 14h4"></path>
|
||||
<path d="M10 18h4"></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
28
src/components/ReactIcons/ClipboardIcon.tsx
Normal file
28
src/components/ReactIcons/ClipboardIcon.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
type ClipboardIconProps = {
|
||||
className?: string;
|
||||
};
|
||||
export function ClipboardIcon(props: ClipboardIconProps) {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
|
||||
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
|
||||
<path d="M12 11h4" />
|
||||
<path d="M12 16h4" />
|
||||
<path d="M8 11h.01" />
|
||||
<path d="M8 16h.01" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
28
src/components/ReactIcons/CogIcon.tsx
Normal file
28
src/components/ReactIcons/CogIcon.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
type CogIconProps = {
|
||||
className?: string;
|
||||
};
|
||||
export function CogIcon(props: CogIconProps) {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="2"
|
||||
stroke="currentColor"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z"
|
||||
/>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
26
src/components/ReactIcons/DropdownIcon.tsx
Normal file
26
src/components/ReactIcons/DropdownIcon.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { cn } from '../../lib/classname';
|
||||
|
||||
type DropdownIconProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function DropdownIcon(props: DropdownIconProps) {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="currentColor"
|
||||
className={cn('h-5 w-5', className)}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M19.5 8.25l-7.5 7.5-7.5-7.5"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
48
src/components/ReactIcons/ErrorIcon2.tsx
Normal file
48
src/components/ReactIcons/ErrorIcon2.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
type ErrorIcon2Props = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function ErrorIcon2(props: ErrorIcon2Props) {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 48 48"
|
||||
className={className}
|
||||
>
|
||||
<linearGradient
|
||||
id="wRKXFJsqHCxLE9yyOYHkza"
|
||||
x1="9.858"
|
||||
x2="38.142"
|
||||
y1="9.858"
|
||||
y2="38.142"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0" stopColor="#f44f5a" />
|
||||
<stop offset=".443" stopColor="#ee3d4a" />
|
||||
<stop offset="1" stopColor="#e52030" />
|
||||
</linearGradient>
|
||||
<path
|
||||
fill="url(#wRKXFJsqHCxLE9yyOYHkza)"
|
||||
d="M44,24c0,11.045-8.955,20-20,20S4,35.045,4,24S12.955,4,24,4S44,12.955,44,24z"
|
||||
/>
|
||||
<path
|
||||
d="M33.192,28.95L28.243,24l4.95-4.95c0.781-0.781,0.781-2.047,0-2.828l-1.414-1.414 c-0.781-0.781-2.047-0.781-2.828,0L24,19.757l-4.95-4.95c-0.781-0.781-2.047-0.781-2.828,0l-1.414,1.414 c-0.781,0.781-0.781,2.047,0,2.828l4.95,4.95l-4.95,4.95c-0.781,0.781-0.781,2.047,0,2.828l1.414,1.414 c0.781,0.781,2.047,0.781,2.828,0l4.95-4.95l4.95,4.95c0.781,0.781,2.047,0.781,2.828,0l1.414-1.414 C33.973,30.997,33.973,29.731,33.192,28.95z"
|
||||
opacity=".05"
|
||||
/>
|
||||
<path
|
||||
d="M32.839,29.303L27.536,24l5.303-5.303c0.586-0.586,0.586-1.536,0-2.121l-1.414-1.414 c-0.586-0.586-1.536-0.586-2.121,0L24,20.464l-5.303-5.303c-0.586-0.586-1.536-0.586-2.121,0l-1.414,1.414 c-0.586,0.586-0.586,1.536,0,2.121L20.464,24l-5.303,5.303c-0.586,0.586-0.586,1.536,0,2.121l1.414,1.414 c0.586,0.586,1.536,0.586,2.121,0L24,27.536l5.303,5.303c0.586,0.586,1.536,0.586,2.121,0l1.414-1.414 C33.425,30.839,33.425,29.889,32.839,29.303z"
|
||||
opacity=".07"
|
||||
/>
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M31.071,15.515l1.414,1.414c0.391,0.391,0.391,1.024,0,1.414L18.343,32.485 c-0.391,0.391-1.024,0.391-1.414,0l-1.414-1.414c-0.391-0.391-0.391-1.024,0-1.414l14.142-14.142 C30.047,15.124,30.681,15.124,31.071,15.515z"
|
||||
/>
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M32.485,31.071l-1.414,1.414c-0.391,0.391-1.024,0.391-1.414,0L15.515,18.343 c-0.391-0.391-0.391-1.024,0-1.414l1.414-1.414c0.391-0.391,1.024-0.391,1.414,0l14.142,14.142 C32.876,30.047,32.876,30.681,32.485,31.071z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
20
src/components/ReactIcons/GitHubIcon.tsx
Normal file
20
src/components/ReactIcons/GitHubIcon.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
type GitHubIconProps = {
|
||||
className?: string;
|
||||
};
|
||||
export function GitHubIcon(props: GitHubIconProps) {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
className={className || ''}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 98 96"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362l-.08-9.127c-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126l-.08 13.526c0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
|
||||
fill="#24292f"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
32
src/components/ReactIcons/GoogleIcon.tsx
Normal file
32
src/components/ReactIcons/GoogleIcon.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
type GoogleIconProps = {
|
||||
className?: string;
|
||||
};
|
||||
export function GoogleIcon(props: GoogleIconProps) {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 90 92"
|
||||
fill="none"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M90 47.1c0-3.1-.3-6.3-.8-9.3H45.9v17.7h24.8c-1 5.7-4.3 10.7-9.2 13.9l14.8 11.5C85 72.8 90 61 90 47.1z"
|
||||
fill="#4280ef"
|
||||
/>
|
||||
<path
|
||||
d="M45.9 91.9c12.4 0 22.8-4.1 30.4-11.1L61.5 69.4c-4.1 2.8-9.4 4.4-15.6 4.4-12 0-22.1-8.1-25.8-18.9L4.9 66.6c7.8 15.5 23.6 25.3 41 25.3z"
|
||||
fill="#34a353"
|
||||
/>
|
||||
<path
|
||||
d="M20.1 54.8c-1.9-5.7-1.9-11.9 0-17.6L4.9 25.4c-6.5 13-6.5 28.3 0 41.2l15.2-11.8z"
|
||||
fill="#f6b704"
|
||||
/>
|
||||
<path
|
||||
d="M45.9 18.3c6.5-.1 12.9 2.4 17.6 6.9L76.6 12C68.3 4.2 57.3 0 45.9.1c-17.4 0-33.2 9.8-41 25.3l15.2 11.8c3.7-10.9 13.8-18.9 25.8-18.9z"
|
||||
fill="#e54335"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
26
src/components/ReactIcons/GroupIcon.tsx
Normal file
26
src/components/ReactIcons/GroupIcon.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
type GroupIconProps = {
|
||||
className?: string;
|
||||
};
|
||||
export function GroupIcon(props: GroupIconProps) {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
|
||||
<circle cx="9" cy="7" r="4" />
|
||||
<path d="M22 21v-2a4 4 0 0 0-3-3.87" />
|
||||
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
23
src/components/ReactIcons/GuideIcon.tsx
Normal file
23
src/components/ReactIcons/GuideIcon.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
type GuideIconProps = {
|
||||
className?: string;
|
||||
};
|
||||
export function GuideIcon(props: GuideIconProps) {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="currentColor"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M9 6.75V15m6-6v8.25m.503 3.498l4.875-2.437c.381-.19.622-.58.622-1.006V4.82c0-.836-.88-1.38-1.628-1.006l-3.869 1.934c-.317.159-.69.159-1.006 0L9.503 3.252a1.125 1.125 0 00-1.006 0L3.622 5.689C3.24 5.88 3 6.27 3 6.695V19.18c0 .836.88 1.38 1.628 1.006l3.869-1.934c.317-.159.69-.159 1.006 0l4.994 2.497c.317.158.69.158 1.006 0z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
23
src/components/ReactIcons/HomeIcon.tsx
Normal file
23
src/components/ReactIcons/HomeIcon.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
type HomeIconProps = {
|
||||
className?: string;
|
||||
};
|
||||
export function HomeIcon(props: HomeIconProps) {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="currentColor"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
49
src/components/ReactIcons/LinkedInIcon.tsx
Normal file
49
src/components/ReactIcons/LinkedInIcon.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
type LinkedInIconProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function LinkedInIcon(props: LinkedInIconProps) {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="100"
|
||||
height="100"
|
||||
viewBox="0,0,256,256"
|
||||
>
|
||||
<g transform="translate(-26.66667,-26.66667) scale(1.20833,1.20833)">
|
||||
<g
|
||||
fill="none"
|
||||
fillRule="nonzero"
|
||||
stroke="none"
|
||||
strokeWidth="1"
|
||||
strokeLinecap="butt"
|
||||
strokeLinejoin="miter"
|
||||
strokeMiterlimit="10"
|
||||
strokeDasharray=""
|
||||
strokeDashoffset="0"
|
||||
fontFamily="none"
|
||||
fontWeight="none"
|
||||
fontSize="none"
|
||||
textAnchor="none"
|
||||
style={{ mixBlendMode: 'normal' }}
|
||||
>
|
||||
<g transform="scale(5.33333,5.33333)">
|
||||
<path
|
||||
d="M42,37c0,2.762 -2.238,5 -5,5h-26c-2.761,0 -5,-2.238 -5,-5v-26c0,-2.762 2.239,-5 5,-5h26c2.762,0 5,2.238 5,5z"
|
||||
fill="#0288d1"
|
||||
></path>
|
||||
<path
|
||||
d="M12,19h5v17h-5zM14.485,17h-0.028c-1.492,0 -2.457,-1.112 -2.457,-2.501c0,-1.419 0.995,-2.499 2.514,-2.499c1.521,0 2.458,1.08 2.486,2.499c0,1.388 -0.965,2.501 -2.515,2.501zM36,36h-5v-9.099c0,-2.198 -1.225,-3.698 -3.192,-3.698c-1.501,0 -2.313,1.012 -2.707,1.99c-0.144,0.35 -0.101,1.318 -0.101,1.807v9h-5v-17h5v2.616c0.721,-1.116 1.85,-2.616 4.738,-2.616c3.578,0 6.261,2.25 6.261,7.274l0.001,9.726z"
|
||||
fill="#ffffff"
|
||||
></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
21
src/components/ReactIcons/MoreVerticalIcon.tsx
Normal file
21
src/components/ReactIcons/MoreVerticalIcon.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
type MoreVerticalIconProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function MoreVerticalIcon(props: MoreVerticalIconProps) {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
clipRule="evenodd"
|
||||
fillRule="evenodd"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit="2"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<path d="m12 16.495c1.242 0 2.25 1.008 2.25 2.25s-1.008 2.25-2.25 2.25-2.25-1.008-2.25-2.25 1.008-2.25 2.25-2.25zm0-6.75c1.242 0 2.25 1.008 2.25 2.25s-1.008 2.25-2.25 2.25-2.25-1.008-2.25-2.25 1.008-2.25 2.25-2.25zm0-6.75c1.242 0 2.25 1.008 2.25 2.25s-1.008 2.25-2.25 2.25-2.25-1.008-2.25-2.25 1.008-2.25 2.25-2.25z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
25
src/components/ReactIcons/RoadmapIcon.tsx
Normal file
25
src/components/ReactIcons/RoadmapIcon.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
type RoadmapIconProps = {
|
||||
className?: string;
|
||||
};
|
||||
export function RoadmapIcon(props: RoadmapIconProps) {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<path d="M18 6H5a2 2 0 0 0-2 2v3a2 2 0 0 0 2 2h13l4-3.5L18 6Z"></path>
|
||||
<path d="M12 13v8"></path>
|
||||
<path d="M12 3v3"></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
24
src/components/ReactIcons/TeamProgressIcon.tsx
Normal file
24
src/components/ReactIcons/TeamProgressIcon.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
type TeamProgressIconProps = {
|
||||
className?: string;
|
||||
};
|
||||
export function TeamProgressIcon(props: TeamProgressIconProps) {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<path d="M3 3v18h18" />
|
||||
<path d="m19 9-5 5-4-4-3 3" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
23
src/components/ReactIcons/TwitterIcon.tsx
Normal file
23
src/components/ReactIcons/TwitterIcon.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
type TwitterIconProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function TwitterIcon(props: TwitterIconProps) {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="15"
|
||||
height="15"
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M8.9285 6.35221L14.5135 0H13.1905L8.339 5.5144L4.467 0H0L5.8565 8.33955L0 15H1.323L6.443 9.17535L10.533 15H15M1.8005 0.976187H3.833L13.1895 14.0718H11.1565"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
21
src/components/ReactIcons/UserIcon.tsx
Normal file
21
src/components/ReactIcons/UserIcon.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
type UserIconProps = {
|
||||
className?: string;
|
||||
};
|
||||
export function UserIcon(props: UserIconProps) {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="currentColor"
|
||||
strokeWidth="0"
|
||||
viewBox="0 0 1024 1024"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<path d="M858.5 763.6a374 374 0 0 0-80.6-119.5 375.63 375.63 0 0 0-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 0 0-80.6 119.5A371.7 371.7 0 0 0 136 901.8a8 8 0 0 0 8 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 0 0 8-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
26
src/components/ReactIcons/UsersIcon.tsx
Normal file
26
src/components/ReactIcons/UsersIcon.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
type UsersIconProps = {
|
||||
className?: string;
|
||||
};
|
||||
export function UsersIcon(props: UsersIconProps) {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
|
||||
<circle cx="9" cy="7" r="4"></circle>
|
||||
<path d="M22 21v-2a4 4 0 0 0-3-3.87"></path>
|
||||
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
78
src/components/ReactIcons/VerifyLetterIcon.tsx
Normal file
78
src/components/ReactIcons/VerifyLetterIcon.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
type VerifyLetterIconProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function VerifyLetterIcon(props: VerifyLetterIconProps) {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
fill="#f79219"
|
||||
d="M222.58,114.782c0-8.69-3.979-16.901-10.8-22.286l-69.526-54.889c-8.357-6.598-20.15-6.598-28.508,0 L44.22,92.496c-6.82,5.385-10.8,13.596-10.8,22.286v12.732H222.58V114.782z"
|
||||
/>
|
||||
<path
|
||||
fill="#ffa91a"
|
||||
d="M213.336,223.341H42.664c-5.105,0-9.244-4.138-9.244-9.244V113.116c0-5.105,4.138-9.244,9.244-9.244 h170.672c5.105,0,9.244,4.139,9.244,9.244v100.981C222.58,219.203,218.441,223.341,213.336,223.341z"
|
||||
/>
|
||||
<path
|
||||
fill="#f79219"
|
||||
d="M213.336,103.872h-0.756v100.225c0,5.105-4.138,9.244-9.244,9.244H33.42v0.756 c0,5.105,4.138,9.244,9.244,9.244h170.672c5.105,0,9.244-4.138,9.244-9.244V113.116 C222.58,108.011,218.441,103.872,213.336,103.872z"
|
||||
/>
|
||||
<path
|
||||
fill="#ef7816"
|
||||
d="M213.336,103.872H42.664c-4.488,0-8.229,3.199-9.067,7.441l79.417,62.697 c8.787,6.937,21.186,6.937,29.973,0l79.417-62.698C221.564,107.071,217.824,103.872,213.336,103.872z"
|
||||
/>
|
||||
<path
|
||||
fill="#f1f2f2"
|
||||
d="M203.33,73.49v52.88l-60.34,47.64c-8.789,6.939-21.191,6.939-29.98,0l-60.34-47.64V73.49 c0-4.418,3.582-8,8-8h134.66C199.748,65.49,203.33,69.072,203.33,73.49z"
|
||||
/>
|
||||
<g>
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M58.67,125.46c-1.101,0-2-0.9-2-2V73.49c0-2.2,1.8-4,4-4h106.89c1.101,0,1.99,0.9,1.99,2s-0.89,2-1.99,2 H60.67v49.97C60.67,124.56,59.77,125.46,58.67,125.46z M175.55,73.49c-1.1,0-2-0.9-2-2s0.9-2,2-2c1.11,0,2,0.9,2,2 S176.66,73.49,175.55,73.49z"
|
||||
/>
|
||||
</g>
|
||||
<g>
|
||||
<path
|
||||
fill="#e6e7e8"
|
||||
d="M195.33,65.49h-2v50.88l-60.34,47.64c-8.789,6.939-21.191,6.939-29.98,0l-50.34-39.745v2.105l60.34,47.64 c8.789,6.939,21.191,6.939,29.98,0l60.34-47.64V73.49C203.33,69.072,199.748,65.49,195.33,65.49z"
|
||||
/>
|
||||
</g>
|
||||
<g>
|
||||
<path
|
||||
fill="#d1d3d4"
|
||||
d="M197.9,65.92c0.274,0.808,0.43,1.67,0.43,2.57v52.88l-60.34,47.64c-8.789,6.939-21.191,6.939-29.98,0 l-55.34-43.692v1.052l60.34,47.64c8.789,6.939,21.191,6.939,29.98,0l60.34-47.64V73.49 C203.33,69.972,201.056,66.991,197.9,65.92z"
|
||||
/>
|
||||
</g>
|
||||
<g>
|
||||
<path
|
||||
fill="#d1d3d4"
|
||||
d="M109.036,99.997H80.422c-1.431,0-2.591-1.16-2.591-2.591v0c0-1.431,1.16-2.591,2.591-2.591h28.614 c1.431,0,2.591,1.16,2.591,2.591v0C111.627,98.836,110.467,99.997,109.036,99.997z"
|
||||
/>
|
||||
<path
|
||||
fill="#d1d3d4"
|
||||
d="M175.578,124.03H80.422c-1.431,0-2.591-1.16-2.591-2.591v0c0-1.431,1.16-2.591,2.591-2.591h95.156 c1.431,0,2.591,1.16,2.591,2.591v0C178.169,122.87,177.009,124.03,175.578,124.03z"
|
||||
/>
|
||||
<path
|
||||
fill="#d1d3d4"
|
||||
d="M175.578,138.881H80.422c-1.431,0-2.591-1.16-2.591-2.591l0,0c0-1.431,1.16-2.591,2.591-2.591h95.156 c1.431,0,2.591,1.16,2.591,2.591l0,0C178.169,137.721,177.009,138.881,175.578,138.881z"
|
||||
/>
|
||||
<polygon
|
||||
fill="#d1d3d4"
|
||||
points="156.425,163.403 99.575,163.403 106.139,168.585 149.861,168.585"
|
||||
/>
|
||||
</g>
|
||||
<g>
|
||||
<polygon
|
||||
fill="#d1d3d4"
|
||||
points="175.236,148.551 80.764,148.551 87.328,153.733 168.672,153.733"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
23
src/components/ReactIcons/VideoIcon.tsx
Normal file
23
src/components/ReactIcons/VideoIcon.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
type VideoIconProps = {
|
||||
className: string;
|
||||
};
|
||||
|
||||
export function VideoIcon(props: VideoIconProps) {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="currentColor"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
d="M15.75 10.5l4.72-4.72a.75.75 0 011.28.53v11.38a.75.75 0 01-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 002.25-2.25v-9a2.25 2.25 0 00-2.25-2.25h-9A2.25 2.25 0 002.25 7.5v9a2.25 2.25 0 002.25 2.25z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { httpGet, httpPatch } from '../lib/http';
|
||||
import BuildingIcon from '../icons/building.svg';
|
||||
import ErrorIcon from '../icons/error.svg';
|
||||
import { pageProgressMessage } from '../stores/page';
|
||||
import type { TeamDocument } from './CreateTeam/CreateTeamForm';
|
||||
import type { AllowedRoles } from './CreateTeam/RoleDropdown';
|
||||
@@ -9,6 +7,8 @@ import type { AllowedMemberStatus } from './TeamDropdown/TeamDropdown';
|
||||
import { isLoggedIn } from '../lib/jwt';
|
||||
import { showLoginPopup } from '../lib/popup';
|
||||
import { getUrlParams } from '../lib/browser';
|
||||
import { ErrorIcon2 } from './ReactIcons/ErrorIcon2';
|
||||
import { BuildingIcon } from './ReactIcons/BuildingIcon';
|
||||
|
||||
type InvitationResponse = {
|
||||
team: TeamDocument;
|
||||
@@ -85,11 +85,7 @@ export function RespondInviteForm() {
|
||||
if (!invite) {
|
||||
return (
|
||||
<div className="container text-center">
|
||||
<img
|
||||
alt={'error'}
|
||||
src={ErrorIcon.src}
|
||||
className="mx-auto mb-4 mt-24 w-20 opacity-20"
|
||||
/>
|
||||
<ErrorIcon2 className="mx-auto mb-4 mt-24 w-20 opacity-20" />
|
||||
|
||||
<h2 className={'mb-1 text-2xl font-bold'}>Error</h2>
|
||||
<p className="mb-4 text-base leading-6 text-gray-600">
|
||||
@@ -110,11 +106,7 @@ export function RespondInviteForm() {
|
||||
|
||||
return (
|
||||
<div className="container text-center">
|
||||
<img
|
||||
alt={'join team'}
|
||||
src={BuildingIcon.src}
|
||||
className="mx-auto mb-4 mt-24 w-20 opacity-20"
|
||||
/>
|
||||
<BuildingIcon className="mx-auto mb-4 mt-24 w-20 opacity-20" />
|
||||
|
||||
<h2 className={'mb-1 text-2xl font-bold'}>Join Team</h2>
|
||||
<p className="mb-3 text-base leading-6 text-gray-600">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCopyText } from '../../hooks/use-copy-text';
|
||||
import CopyIcon from '../../icons/copy.svg';
|
||||
import { CopyIcon } from 'lucide-react';
|
||||
|
||||
type EditorProps = {
|
||||
title: string;
|
||||
@@ -20,11 +20,11 @@ export function Editor(props: EditorProps) {
|
||||
<button className="flex items-center" onClick={() => copyText(text)}>
|
||||
{isCopied && (
|
||||
<span className="mr-1 text-xs leading-none text-gray-700">
|
||||
Copied!
|
||||
Copied!
|
||||
</span>
|
||||
)}
|
||||
|
||||
<img src={CopyIcon.src} alt="Copy" className="inline-block h-4 w-4" />
|
||||
<CopyIcon className="inline-block h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
|
||||
@@ -2,13 +2,13 @@ import { useState } from 'react';
|
||||
|
||||
import { useCopyText } from '../../hooks/use-copy-text';
|
||||
import { useAuth } from '../../hooks/use-auth';
|
||||
import CopyIcon from '../../icons/copy.svg';
|
||||
import { RoadmapSelect } from './RoadmapSelect';
|
||||
import { GitHubReadmeBanner } from './GitHubReadmeBanner';
|
||||
import { downloadImage } from '../../helper/download-image';
|
||||
import { SelectionButton } from './SelectionButton';
|
||||
import { StepCounter } from './StepCounter';
|
||||
import { Editor } from './Editor';
|
||||
import { CopyIcon } from 'lucide-react';
|
||||
|
||||
type StepLabelProps = {
|
||||
label: string;
|
||||
@@ -34,7 +34,7 @@ export function RoadCardPage() {
|
||||
}
|
||||
|
||||
const badgeUrl = new URL(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-badge/${version}/${user?.id}`
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-badge/${version}/${user?.id}`,
|
||||
);
|
||||
|
||||
badgeUrl.searchParams.set('variant', variant);
|
||||
@@ -44,7 +44,7 @@ export function RoadCardPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-start gap-4 mx-0 sm:-mx-10 px-0 sm:px-10 border-b pt-2 pb-4">
|
||||
<div className="mx-0 flex items-start gap-4 border-b px-0 pb-4 pt-2 sm:-mx-10 sm:px-10">
|
||||
<StepCounter step={1} />
|
||||
<div>
|
||||
<StepLabel label="Pick progress to show (Max. 4)" />
|
||||
@@ -58,7 +58,7 @@ export function RoadCardPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-4 mx-0 sm:-mx-10 px-0 sm:px-10 border-b py-4">
|
||||
<div className="mx-0 flex items-start gap-4 border-b px-0 py-4 sm:-mx-10 sm:px-10">
|
||||
<StepCounter step={2} />
|
||||
<div>
|
||||
<StepLabel label="Select Mode (Dark vs Light)" />
|
||||
@@ -85,7 +85,7 @@ export function RoadCardPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-4 mx-0 sm:-mx-10 px-0 sm:px-10 border-b py-4">
|
||||
<div className="mx-0 flex items-start gap-4 border-b px-0 py-4 sm:-mx-10 sm:px-10">
|
||||
<StepCounter step={3} />
|
||||
<div>
|
||||
<StepLabel label="Select Version" />
|
||||
@@ -111,7 +111,7 @@ export function RoadCardPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-4 mx-0 sm:-mx-10 px-0 sm:px-10 border-b py-4">
|
||||
<div className="mx-0 flex items-start gap-4 border-b px-0 py-4 sm:-mx-10 sm:px-10">
|
||||
<StepCounter step={4} />
|
||||
<div className="flex-grow">
|
||||
<StepLabel label="Share your #RoadCard with others" />
|
||||
@@ -146,7 +146,7 @@ export function RoadCardPage() {
|
||||
className="flex cursor-pointer items-center justify-center rounded border border-gray-300 p-1.5 px-2 text-sm font-medium disabled:bg-blue-50"
|
||||
onClick={() => copyText(badgeUrl.toString())}
|
||||
>
|
||||
<img alt="Copy" src={CopyIcon.src} className="mr-1" />
|
||||
<CopyIcon size={16} className="inline-block h-4 w-4 mr-1" />
|
||||
|
||||
{isCopied ? 'Copied!' : 'Copy Link'}
|
||||
</button>
|
||||
|
||||
@@ -10,6 +10,9 @@ import { MarkFavorite } from './FeaturedItems/MarkFavorite';
|
||||
import { TeamVersions } from './TeamVersions/TeamVersions';
|
||||
import { CreateVersion } from './CreateVersion/CreateVersion';
|
||||
import { type RoadmapFrontmatter } from '../lib/roadmap';
|
||||
import { ShareRoadmapButton } from './ShareRoadmapButton';
|
||||
import { Share2 } from 'lucide-react';
|
||||
import ShareIcons from './ShareIcons/ShareIcons.astro';
|
||||
|
||||
export interface Props {
|
||||
title: string;
|
||||
@@ -96,6 +99,12 @@ const hasTnsBanner = !!tnsBannerLink;
|
||||
←<span class='hidden sm:inline'> All Roadmaps</span>
|
||||
</a>
|
||||
|
||||
<ShareRoadmapButton
|
||||
description={description}
|
||||
pageUrl={`https://roadmap.sh/${roadmapId}`}
|
||||
client:idle
|
||||
/>
|
||||
|
||||
{isRoadmapReady && (
|
||||
<>
|
||||
<button
|
||||
@@ -120,16 +129,6 @@ const hasTnsBanner = !!tnsBannerLink;
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
|
||||
<button
|
||||
data-guest-required
|
||||
data-popup='login-popup'
|
||||
class='inline-flex hidden items-center justify-center rounded-md bg-yellow-400 px-3 py-1.5 text-xs font-medium hover:bg-yellow-500 sm:text-sm'
|
||||
aria-label='Subscribe for Updates'
|
||||
>
|
||||
<Icon icon='email' />
|
||||
<span class='ml-2'>Subscribe</span>
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ const redditUrl = `https://www.reddit.com/submit?title=${description}&url=${page
|
||||
---
|
||||
|
||||
<div class='absolute left-[-18px] top-[110px] h-full hidden' id='page-share-icons'>
|
||||
<div class='flex sticky top-[100px] flex-col gap-1.5'>
|
||||
<a href={twitterUrl} target='_blank' class='text-gray-500 hover:text-gray-700'>
|
||||
<div class='flex sticky top-[100px] flex-col gap-1.5 items-center'>
|
||||
<a href={twitterUrl} target='_blank' class='text-gray-500 hover:text-gray-700 mb-0.5'>
|
||||
<Icon icon='twitter' />
|
||||
</a>
|
||||
<a href={fbUrl} target='_blank' class='text-gray-500 hover:text-gray-700'>
|
||||
|
||||
136
src/components/ShareRoadmapButton.tsx
Normal file
136
src/components/ShareRoadmapButton.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import { Check, Copy, Facebook, Linkedin, Share2, Twitter } from 'lucide-react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useOutsideClick } from '../hooks/use-outside-click.ts';
|
||||
import { useCopyText } from '../hooks/use-copy-text.ts';
|
||||
import { cn } from '../lib/classname.ts';
|
||||
import { TwitterIcon } from './ReactIcons/TwitterIcon.tsx';
|
||||
|
||||
type ShareRoadmapButtonProps = {
|
||||
description: string;
|
||||
pageUrl: string;
|
||||
};
|
||||
|
||||
export function ShareRoadmapButton(props: ShareRoadmapButtonProps) {
|
||||
const { description, pageUrl } = props;
|
||||
|
||||
const { isCopied, copyText } = useCopyText();
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
|
||||
const twitterUrl = `https://twitter.com/intent/tweet?text=${description}&url=${pageUrl}`;
|
||||
const fbUrl = `https://www.facebook.com/sharer/sharer.php?quote=${description}&u=${pageUrl}`;
|
||||
const hnUrl = `https://news.ycombinator.com/submitlink?t=${description}&u=${pageUrl}`;
|
||||
const redditUrl = `https://www.reddit.com/submit?title=${description}&url=${pageUrl}`;
|
||||
const linkedinUrl = `https://www.linkedin.com/shareArticle?mini=true&url=${pageUrl}&title=${description}`;
|
||||
|
||||
useOutsideClick(containerRef, () => {
|
||||
setIsDropdownOpen(false);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="relative" ref={containerRef}>
|
||||
<button
|
||||
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
|
||||
className={cn(
|
||||
'inline-flex items-center justify-center rounded-md bg-yellow-400 px-3 py-1.5 text-xs font-medium hover:bg-yellow-500 sm:text-sm',
|
||||
{
|
||||
'bg-yellow-500': isDropdownOpen,
|
||||
'bg-green-400': isCopied,
|
||||
},
|
||||
)}
|
||||
aria-label="Share Roadmap"
|
||||
>
|
||||
{!isCopied && (
|
||||
<>
|
||||
<Share2 size="15px" />
|
||||
<span className="ml-2 hidden sm:inline">Share</span>
|
||||
</>
|
||||
)}
|
||||
{isCopied && (
|
||||
<>
|
||||
<Check size="15px" />
|
||||
<span className="ml-2 hidden sm:inline">Copied</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{isDropdownOpen && (
|
||||
<div className="absolute left-0 z-50 mt-1 w-44 rounded-md bg-slate-800 text-sm text-white shadow-lg ring-1 ring-black ring-opacity-5">
|
||||
<div className="flex flex-col px-1 py-1">
|
||||
<button
|
||||
onClick={() => {
|
||||
copyText(pageUrl);
|
||||
setIsDropdownOpen(false);
|
||||
}}
|
||||
className="flex w-full items-center gap-2 rounded-sm px-2 py-2 text-sm text-slate-100 hover:bg-slate-700"
|
||||
>
|
||||
<div className="flex w-[20px] items-center justify-center">
|
||||
<Copy size="15px" className="text-slate-400" />
|
||||
</div>
|
||||
Copy Link
|
||||
</button>
|
||||
<a
|
||||
href={twitterUrl}
|
||||
target={'_blank'}
|
||||
className="mt-1 flex w-full items-center gap-2 rounded-sm px-2 py-2 text-sm text-slate-100 hover:bg-slate-700"
|
||||
>
|
||||
<div className="flex w-[20px] items-center justify-center">
|
||||
<TwitterIcon className="h-[16px] text-slate-400" />
|
||||
</div>
|
||||
Twitter
|
||||
</a>
|
||||
<a
|
||||
href={fbUrl}
|
||||
target={'_blank'}
|
||||
className="flex w-full items-center gap-2 rounded-sm px-2 py-2 text-sm text-slate-100 hover:bg-slate-700"
|
||||
>
|
||||
<div className="flex w-[20px] items-center justify-center">
|
||||
<Facebook size="16px" className="text-slate-400" />
|
||||
</div>
|
||||
Facebook
|
||||
</a>
|
||||
<a
|
||||
href={hnUrl}
|
||||
target={'_blank'}
|
||||
className="flex w-full items-center gap-2 rounded-sm px-2 py-2 text-sm text-slate-100 hover:bg-slate-700"
|
||||
>
|
||||
<div className="flex w-[20px] items-center justify-center">
|
||||
<img
|
||||
alt={'hackernews logo'}
|
||||
src={'/images/hackernews.svg'}
|
||||
className="h-5 w-5"
|
||||
/>
|
||||
</div>
|
||||
Hacker News
|
||||
</a>
|
||||
<a
|
||||
href={redditUrl}
|
||||
target={'_blank'}
|
||||
className="flex w-full items-center gap-2 rounded-sm px-2 py-2 text-sm text-slate-100 hover:bg-slate-700"
|
||||
>
|
||||
<div className="flex w-[20px] items-center justify-center">
|
||||
<img
|
||||
alt={'reddit logo'}
|
||||
src={'/images/reddit.svg'}
|
||||
className="h-5 w-5"
|
||||
/>
|
||||
</div>
|
||||
Reddit
|
||||
</a>
|
||||
<a
|
||||
href={linkedinUrl}
|
||||
target={'_blank'}
|
||||
className="flex w-full items-center gap-2 rounded-sm px-2 py-2 text-sm text-slate-100 hover:bg-slate-700"
|
||||
>
|
||||
<div className="flex w-[20px] items-center justify-center">
|
||||
<Linkedin size="16px" className="text-slate-400" />
|
||||
</div>
|
||||
LinkedIn
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import ChevronDown from '../../icons/dropdown.svg';
|
||||
import { httpGet } from '../../lib/http';
|
||||
import { useAuth } from '../../hooks/use-auth';
|
||||
import { useOutsideClick } from '../../hooks/use-outside-click';
|
||||
@@ -10,6 +9,7 @@ import { useStore } from '@nanostores/react';
|
||||
import { useTeamId } from '../../hooks/use-team-id';
|
||||
import { useToast } from '../../hooks/use-toast';
|
||||
import type { ValidTeamType } from '../CreateTeam/Step0';
|
||||
import { DropdownIcon } from '../ReactIcons/DropdownIcon.tsx';
|
||||
|
||||
const allowedStatus = ['invited', 'joined', 'rejected'] as const;
|
||||
export type AllowedMemberStatus = (typeof allowedStatus)[number];
|
||||
@@ -44,7 +44,7 @@ export function TeamDropdown() {
|
||||
if (shouldShowTeamIndicator) {
|
||||
localStorage.setItem(
|
||||
'viewedTeamsCount',
|
||||
(viewedTeamsCountNumber + 1).toString()
|
||||
(viewedTeamsCountNumber + 1).toString(),
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
@@ -67,7 +67,7 @@ export function TeamDropdown() {
|
||||
|
||||
async function getAllTeams() {
|
||||
const { response, error } = await httpGet<TeamListResponse>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-get-user-teams`
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-get-user-teams`,
|
||||
);
|
||||
if (error || !response) {
|
||||
toast.error(error?.message || 'Something went wrong');
|
||||
@@ -140,7 +140,7 @@ export function TeamDropdown() {
|
||||
{isLoading && 'Loading ..'}
|
||||
</span>
|
||||
</div>
|
||||
<img alt={'show dropdown'} src={ChevronDown.src} className="h-4 w-4" />
|
||||
<DropdownIcon className={'h-4 w-4'} />
|
||||
</button>
|
||||
|
||||
{showDropdown && (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import type { TeamMemberDocument } from './TeamMembersPage';
|
||||
import MoreIcon from '../../icons/more-vertical.svg';
|
||||
import { useOutsideClick } from '../../hooks/use-outside-click';
|
||||
import { MoreVerticalIcon } from '../ReactIcons/MoreVerticalIcon.tsx';
|
||||
|
||||
export function MemberActionDropdown({
|
||||
member,
|
||||
@@ -79,7 +79,7 @@ export function MemberActionDropdown({
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="ml-2 flex items-center opacity-60 transition-opacity hover:opacity-100 disabled:cursor-not-allowed disabled:opacity-30"
|
||||
>
|
||||
<img alt="menu" src={MoreIcon.src} className="h-4 w-4" />
|
||||
<MoreVerticalIcon className="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
{isOpen && (
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useState } from 'react';
|
||||
import type { GroupByRoadmap, TeamMember } from './TeamProgressPage';
|
||||
import { getUrlParams } from '../../lib/browser';
|
||||
import ExternalLinkIcon from '../../icons/external-link.svg';
|
||||
import { useAuth } from '../../hooks/use-auth';
|
||||
import { LucideExternalLink } from 'lucide-react';
|
||||
|
||||
type GroupRoadmapItemProps = {
|
||||
roadmap: GroupByRoadmap;
|
||||
@@ -33,11 +33,7 @@ export function GroupRoadmapItem(props: GroupRoadmapItemProps) {
|
||||
className="group mb-0.5 flex shrink-0 items-center justify-between text-base font-medium leading-none text-black"
|
||||
target={'_blank'}
|
||||
>
|
||||
<img
|
||||
alt={'link'}
|
||||
src={ExternalLinkIcon.src}
|
||||
className="ml-2 h-4 w-4 opacity-20 transition-opacity group-hover:opacity-100"
|
||||
/>
|
||||
<LucideExternalLink className="h-4 w-4 opacity-20 transition-opacity group-hover:opacity-100" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -58,7 +54,7 @@ export function GroupRoadmapItem(props: GroupRoadmapItemProps) {
|
||||
onClick={() => {
|
||||
onShowResourceProgress(
|
||||
member.member,
|
||||
member.progress?.resourceId!
|
||||
member.progress?.resourceId!,
|
||||
);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -25,6 +25,7 @@ import type { Node } from 'reactflow';
|
||||
import { useKeydown } from '../../hooks/use-keydown';
|
||||
import { useOutsideClick } from '../../hooks/use-outside-click';
|
||||
import { MemberProgressModalHeader } from './MemberProgressModalHeader';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
export type ProgressMapProps = {
|
||||
member: TeamMember;
|
||||
@@ -284,7 +285,7 @@ export function MemberCustomProgressModal(props: ProgressMapProps) {
|
||||
}`}
|
||||
onClick={onClose}
|
||||
>
|
||||
<img alt={'close'} src={CloseIcon.src} className="h-4 w-4" />
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close modal</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -12,12 +12,12 @@ import {
|
||||
type ResourceType,
|
||||
updateResourceProgress,
|
||||
} from '../../lib/resource-progress';
|
||||
import CloseIcon from '../../icons/close.svg';
|
||||
import { useToast } from '../../hooks/use-toast';
|
||||
import { useAuth } from '../../hooks/use-auth';
|
||||
import { pageProgressMessage } from '../../stores/page';
|
||||
import { MemberProgressModalHeader } from './MemberProgressModalHeader';
|
||||
import {replaceChildren} from "../../lib/dom.ts";
|
||||
import { replaceChildren } from '../../lib/dom.ts';
|
||||
import { XIcon } from 'lucide-react';
|
||||
|
||||
export type ProgressMapProps = {
|
||||
member: TeamMember;
|
||||
@@ -68,12 +68,12 @@ export function MemberProgressModal(props: ProgressMapProps) {
|
||||
teamId: string,
|
||||
memberId: string,
|
||||
resourceType: string,
|
||||
resourceId: string
|
||||
resourceId: string,
|
||||
) {
|
||||
const { error, response } = await httpGet<MemberProgressResponse>(
|
||||
`${
|
||||
import.meta.env.PUBLIC_API_URL
|
||||
}/v1-get-member-resource-progress/${teamId}/${memberId}?resourceType=${resourceType}&resourceId=${resourceId}`
|
||||
}/v1-get-member-resource-progress/${teamId}/${memberId}?resourceType=${resourceType}&resourceId=${resourceId}`,
|
||||
);
|
||||
if (error || !response) {
|
||||
toast.error(error?.message || 'Failed to get member progress');
|
||||
@@ -160,14 +160,14 @@ export function MemberProgressModal(props: ProgressMapProps) {
|
||||
resourceType: resourceType as ResourceType,
|
||||
topicId,
|
||||
},
|
||||
newStatus
|
||||
newStatus,
|
||||
)
|
||||
.then(() => {
|
||||
renderTopicProgress(topicId, newStatus);
|
||||
getMemberProgress(teamId, member._id, resourceType, resourceId).then(
|
||||
(data) => {
|
||||
setMemberProgress(data);
|
||||
}
|
||||
},
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -227,7 +227,7 @@ export function MemberProgressModal(props: ProgressMapProps) {
|
||||
e.preventDefault();
|
||||
updateTopicStatus(
|
||||
topicId,
|
||||
!isCurrentStatusLearning ? 'learning' : 'pending'
|
||||
!isCurrentStatusLearning ? 'learning' : 'pending',
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -236,7 +236,7 @@ export function MemberProgressModal(props: ProgressMapProps) {
|
||||
e.preventDefault();
|
||||
updateTopicStatus(
|
||||
topicId,
|
||||
!isCurrentStatusSkipped ? 'skipped' : 'pending'
|
||||
!isCurrentStatusSkipped ? 'skipped' : 'pending',
|
||||
);
|
||||
|
||||
return;
|
||||
@@ -298,7 +298,8 @@ export function MemberProgressModal(props: ProgressMapProps) {
|
||||
}`}
|
||||
onClick={onClose}
|
||||
>
|
||||
<img alt={'close'} src={CloseIcon.src} className="h-4 w-4" />
|
||||
<XIcon className="h-4 w-4" />
|
||||
|
||||
<span className="sr-only">Close modal</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import MoreIcon from '../../icons/more-vertical.svg';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useOutsideClick } from '../../hooks/use-outside-click';
|
||||
import { Lock, MoreVertical, Shapes, Trash2 } from 'lucide-react';
|
||||
import { MoreVerticalIcon } from '../ReactIcons/MoreVerticalIcon.tsx';
|
||||
|
||||
type RoadmapActionDropdownProps = {
|
||||
onDelete?: () => void;
|
||||
@@ -26,7 +26,7 @@ export function RoadmapActionDropdown(props: RoadmapActionDropdownProps) {
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="hidden items-center opacity-60 transition-opacity hover:opacity-100 disabled:cursor-not-allowed disabled:opacity-30 sm:flex"
|
||||
>
|
||||
<img alt="menu" src={MoreIcon.src} className="h-4 w-4" />
|
||||
<MoreVerticalIcon className={'h-4 w-4'} />
|
||||
</button>
|
||||
<button
|
||||
disabled={false}
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { TeamDocument } from '../CreateTeam/CreateTeamForm';
|
||||
import type { TeamResourceConfig } from '../CreateTeam/RoadmapSelector';
|
||||
import { httpGet, httpPut } from '../../lib/http';
|
||||
import { pageProgressMessage } from '../../stores/page';
|
||||
import RoadmapIcon from '../../icons/roadmap.svg';
|
||||
import type { PageType } from '../CommandMenu/CommandMenu';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { $canManageCurrentTeam } from '../../stores/team';
|
||||
@@ -28,6 +27,7 @@ import { RoadmapActionDropdown } from './RoadmapActionDropdown';
|
||||
import { UpdateTeamResourceModal } from '../CreateTeam/UpdateTeamResourceModal';
|
||||
import { ShareOptionsModal } from '../ShareOptions/ShareOptionsModal';
|
||||
import { cn } from '../../lib/classname';
|
||||
import { RoadmapIcon } from '../ReactIcons/RoadmapIcon.tsx';
|
||||
|
||||
export function TeamRoadmaps() {
|
||||
const { t: teamId } = getUrlParams();
|
||||
@@ -73,7 +73,7 @@ export function TeamRoadmaps() {
|
||||
|
||||
async function loadTeam(teamIdToFetch: string) {
|
||||
const { response, error } = await httpGet<TeamDocument>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-get-team/${teamIdToFetch}`
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-get-team/${teamIdToFetch}`,
|
||||
);
|
||||
|
||||
if (error || !response) {
|
||||
@@ -87,7 +87,7 @@ export function TeamRoadmaps() {
|
||||
|
||||
async function loadTeamResourceConfig(teamId: string) {
|
||||
const { error, response } = await httpGet<TeamResourceConfig>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-get-team-resource-config/${teamId}`
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-get-team-resource-config/${teamId}`,
|
||||
);
|
||||
if (error || !Array.isArray(response)) {
|
||||
console.error(error);
|
||||
@@ -127,7 +127,7 @@ export function TeamRoadmaps() {
|
||||
{
|
||||
resourceId: roadmapId,
|
||||
resourceType: 'roadmap',
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (error || !response) {
|
||||
@@ -156,7 +156,7 @@ export function TeamRoadmaps() {
|
||||
resourceId: roadmapId,
|
||||
resourceType: 'roadmap',
|
||||
removed: [],
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (error || !response) {
|
||||
@@ -190,13 +190,13 @@ export function TeamRoadmaps() {
|
||||
}
|
||||
window.addEventListener(
|
||||
'custom-roadmap-created',
|
||||
handleCustomRoadmapCreated
|
||||
handleCustomRoadmapCreated,
|
||||
);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener(
|
||||
'custom-roadmap-created',
|
||||
handleCustomRoadmapCreated
|
||||
handleCustomRoadmapCreated,
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
@@ -252,13 +252,13 @@ export function TeamRoadmaps() {
|
||||
);
|
||||
|
||||
const placeholderRoadmaps = teamResources.filter(
|
||||
(c: TeamResourceConfig[0]) => c.isCustomResource && !c.topics
|
||||
(c: TeamResourceConfig[0]) => c.isCustomResource && !c.topics,
|
||||
);
|
||||
const customRoadmaps = teamResources.filter(
|
||||
(c: TeamResourceConfig[0]) => c.isCustomResource && c.topics
|
||||
(c: TeamResourceConfig[0]) => c.isCustomResource && c.topics,
|
||||
);
|
||||
const defaultRoadmaps = teamResources.filter(
|
||||
(c: TeamResourceConfig[0]) => !c.isCustomResource
|
||||
(c: TeamResourceConfig[0]) => !c.isCustomResource,
|
||||
);
|
||||
|
||||
const hasRoadmaps =
|
||||
@@ -272,11 +272,8 @@ export function TeamRoadmaps() {
|
||||
{addRoadmapModal}
|
||||
{createRoadmapModal}
|
||||
|
||||
<img
|
||||
alt="roadmap"
|
||||
src={RoadmapIcon.src}
|
||||
className="mb-4 h-24 w-24 opacity-10"
|
||||
/>
|
||||
<RoadmapIcon className="mb-4 h-24 w-24 opacity-10" />
|
||||
|
||||
<h3 className="mb-1 text-2xl font-bold text-gray-900">No roadmaps</h3>
|
||||
<p className="text-base text-gray-500">
|
||||
{canManageCurrentTeam
|
||||
@@ -380,11 +377,11 @@ export function TeamRoadmaps() {
|
||||
onDelete={() => {
|
||||
if (
|
||||
confirm(
|
||||
'Are you sure you want to remove this roadmap?'
|
||||
'Are you sure you want to remove this roadmap?',
|
||||
)
|
||||
) {
|
||||
onRemove(resourceConfig.resourceId).finally(
|
||||
() => {}
|
||||
() => {},
|
||||
);
|
||||
}
|
||||
}}
|
||||
@@ -405,7 +402,7 @@ export function TeamRoadmaps() {
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -433,7 +430,7 @@ export function TeamRoadmaps() {
|
||||
'grid grid-cols-1 p-2.5',
|
||||
canManageCurrentTeam
|
||||
? 'sm:grid-cols-[auto_172px]'
|
||||
: 'sm:grid-cols-[auto_110px]'
|
||||
: 'sm:grid-cols-[auto_110px]',
|
||||
)}
|
||||
key={resourceConfig.resourceId}
|
||||
>
|
||||
@@ -464,11 +461,11 @@ export function TeamRoadmaps() {
|
||||
onDelete={() => {
|
||||
if (
|
||||
confirm(
|
||||
'Are you sure you want to remove this roadmap?'
|
||||
'Are you sure you want to remove this roadmap?',
|
||||
)
|
||||
) {
|
||||
onRemove(resourceConfig.resourceId).finally(
|
||||
() => {}
|
||||
() => {},
|
||||
);
|
||||
}
|
||||
}}
|
||||
@@ -557,11 +554,11 @@ export function TeamRoadmaps() {
|
||||
onDelete={() => {
|
||||
if (
|
||||
confirm(
|
||||
'Are you sure you want to remove this roadmap?'
|
||||
'Are you sure you want to remove this roadmap?',
|
||||
)
|
||||
) {
|
||||
onRemove(resourceConfig.resourceId).finally(
|
||||
() => {}
|
||||
() => {},
|
||||
);
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { TeamDropdown } from './TeamDropdown/TeamDropdown';
|
||||
import ChevronDown from '../icons/dropdown.svg';
|
||||
import { useTeamId } from '../hooks/use-team-id';
|
||||
import TeamProgress from '../icons/team-progress.svg';
|
||||
import SettingsIcon from '../icons/cog.svg';
|
||||
import ChatIcon from '../icons/chat.svg';
|
||||
import MapIcon from '../icons/map.svg';
|
||||
import GroupIcon from '../icons/group.svg';
|
||||
import { useState } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { $currentTeam } from '../stores/team';
|
||||
import { SubmitFeedbackPopup } from './Feedback/SubmitFeedbackPopup';
|
||||
import { ChevronDownIcon } from './ReactIcons/ChevronDownIcon.tsx';
|
||||
import { GroupIcon } from './ReactIcons/GroupIcon.tsx';
|
||||
import { TeamProgressIcon } from './ReactIcons/TeamProgressIcon.tsx';
|
||||
import { MapIcon, MessageCircle } from 'lucide-react';
|
||||
import { CogIcon } from './ReactIcons/CogIcon.tsx';
|
||||
|
||||
type TeamSidebarProps = {
|
||||
activePageId: string;
|
||||
@@ -29,26 +29,26 @@ export function TeamSidebar({ activePageId, children }: TeamSidebarProps) {
|
||||
title: 'Progress',
|
||||
href: `/team/progress?t=${teamId}`,
|
||||
id: 'progress',
|
||||
icon: TeamProgress.src,
|
||||
icon: TeamProgressIcon,
|
||||
},
|
||||
{
|
||||
title: 'Roadmaps',
|
||||
href: `/team/roadmaps?t=${teamId}`,
|
||||
id: 'roadmaps',
|
||||
icon: MapIcon.src,
|
||||
icon: MapIcon,
|
||||
hasWarning: currentTeam?.roadmaps?.length === 0,
|
||||
},
|
||||
{
|
||||
title: 'Members',
|
||||
href: `/team/members?t=${teamId}`,
|
||||
id: 'members',
|
||||
icon: GroupIcon.src,
|
||||
icon: GroupIcon,
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
href: `/team/settings?t=${teamId}`,
|
||||
id: 'settings',
|
||||
icon: SettingsIcon.src,
|
||||
icon: CogIcon,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -66,7 +66,7 @@ export function TeamSidebar({ activePageId, children }: TeamSidebarProps) {
|
||||
sidebarLinks.find((sidebarLink) => sidebarLink.id === activePageId)
|
||||
?.title
|
||||
}
|
||||
<img alt="menu" src={ChevronDown.src} className="h-4 w-4" />
|
||||
<ChevronDownIcon className="h-4 w-4" />
|
||||
</button>
|
||||
{menuShown && (
|
||||
<ul
|
||||
@@ -80,7 +80,7 @@ export function TeamSidebar({ activePageId, children }: TeamSidebarProps) {
|
||||
activePageId === 'team' ? 'bg-slate-100' : ''
|
||||
}`}
|
||||
>
|
||||
<img alt={'teams'} src={GroupIcon.src} className={`mr-2 h-4 w-4`} />
|
||||
<GroupIcon className="mr-2 h-4 w-4" />
|
||||
Personal Account / Teams
|
||||
</a>
|
||||
</li>
|
||||
@@ -95,11 +95,8 @@ export function TeamSidebar({ activePageId, children }: TeamSidebarProps) {
|
||||
isActive ? 'bg-slate-100' : ''
|
||||
}`}
|
||||
>
|
||||
<img
|
||||
alt={'menu icon'}
|
||||
src={sidebarLink.icon}
|
||||
className="mr-2 h-4 w-4"
|
||||
/>
|
||||
{<sidebarLink.icon className="mr-2 h-4 w-4" />}
|
||||
|
||||
{sidebarLink.title}
|
||||
</a>
|
||||
</li>
|
||||
@@ -111,11 +108,7 @@ export function TeamSidebar({ activePageId, children }: TeamSidebarProps) {
|
||||
className={`flex w-full items-center rounded px-3 py-1.5 text-sm text-slate-900 hover:bg-slate-200`}
|
||||
onClick={() => setShowFeedbackPopup(true)}
|
||||
>
|
||||
<img
|
||||
alt={'menu icon'}
|
||||
src={ChatIcon.src}
|
||||
className="mr-2 h-4 w-4"
|
||||
/>
|
||||
<MessageCircle className="mr-2 h-4 w-4" />
|
||||
Send Feedback
|
||||
</button>
|
||||
</li>
|
||||
@@ -150,11 +143,8 @@ export function TeamSidebar({ activePageId, children }: TeamSidebarProps) {
|
||||
>
|
||||
<span className="flex flex-grow items-center justify-between">
|
||||
<span className="flex">
|
||||
<img
|
||||
alt="menu icon"
|
||||
src={sidebarLink.icon}
|
||||
className="relative top-[2px] mr-2 h-4 w-4"
|
||||
/>
|
||||
{<sidebarLink.icon className="mr-2 h-4 w-4" />}
|
||||
|
||||
{sidebarLink.title}
|
||||
</span>
|
||||
{sidebarLink.hasWarning && (
|
||||
@@ -174,7 +164,7 @@ export function TeamSidebar({ activePageId, children }: TeamSidebarProps) {
|
||||
className="mr-3 mt-4 flex items-center justify-center rounded-md border p-2 text-sm text-gray-500 transition-colors hover:border-gray-300 hover:bg-gray-50 hover:text-black"
|
||||
onClick={() => setShowFeedbackPopup(true)}
|
||||
>
|
||||
<img alt={'feedback'} src={ChatIcon.src} className="mr-2 h-4 w-4" />
|
||||
<MessageCircle className="mr-2 h-4 w-4" />
|
||||
Send Feedback
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState, useEffect, useRef } from 'react';
|
||||
import type { TeamDocument } from '../CreateTeam/CreateTeamForm';
|
||||
import type { TeamResourceConfig } from '../CreateTeam/RoadmapSelector';
|
||||
import { httpGet } from '../../lib/http';
|
||||
import DropdownIcon from '../../icons/dropdown.svg';
|
||||
// import DropdownIcon from '../../icons/dropdown.svg';
|
||||
import {
|
||||
clearResourceProgress,
|
||||
refreshProgressCounters,
|
||||
@@ -15,6 +15,7 @@ import { useKeydown } from '../../hooks/use-keydown';
|
||||
import { isLoggedIn } from '../../lib/jwt';
|
||||
import { useAuth } from '../../hooks/use-auth';
|
||||
import { useToast } from '../../hooks/use-toast';
|
||||
import { DropdownIcon } from '../ReactIcons/DropdownIcon';
|
||||
|
||||
type TeamVersionsProps = {
|
||||
resourceId: string;
|
||||
@@ -75,7 +76,7 @@ export function TeamVersions(props: TeamVersionsProps) {
|
||||
}/v1-get-team-versions?${new URLSearchParams({
|
||||
resourceId,
|
||||
resourceType,
|
||||
})}`
|
||||
})}`,
|
||||
);
|
||||
|
||||
if (error || !response) {
|
||||
@@ -142,11 +143,7 @@ export function TeamVersions(props: TeamVersionsProps) {
|
||||
<span className="truncate">
|
||||
{selectedTeamVersion?.team.name || 'Team Versions'}
|
||||
</span>
|
||||
<img
|
||||
alt="Dropdown"
|
||||
src={DropdownIcon.src}
|
||||
className="h-3 w-3 sm:h-4 sm:w-4"
|
||||
/>
|
||||
<DropdownIcon className="h-3 w-3 sm:h-4 sm:w-4" />
|
||||
</div>
|
||||
<div className="sm:hidden">
|
||||
{shouldShowAvatar ? (
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import CloseIcon from '../../icons/close.svg';
|
||||
import SpinnerIcon from '../../icons/spinner.svg';
|
||||
|
||||
import { useKeydown } from '../../hooks/use-keydown';
|
||||
import { useLoadTopic } from '../../hooks/use-load-topic';
|
||||
@@ -26,8 +24,9 @@ import type {
|
||||
} from '../CustomRoadmap/CustomRoadmap';
|
||||
import { markdownToHtml } from '../../lib/markdown';
|
||||
import { cn } from '../../lib/classname';
|
||||
import { Ban, FileText } from 'lucide-react';
|
||||
import { Ban, FileText, X } from 'lucide-react';
|
||||
import { getUrlParams } from '../../lib/browser';
|
||||
import { Spinner } from '../ReactIcons/Spinner';
|
||||
|
||||
type TopicDetailProps = {
|
||||
canSubmitContribution: boolean;
|
||||
@@ -203,10 +202,10 @@ export function TopicDetail(props: TopicDetailProps) {
|
||||
>
|
||||
{isLoading && (
|
||||
<div className="flex w-full justify-center">
|
||||
<img
|
||||
src={SpinnerIcon.src}
|
||||
alt="Loading"
|
||||
className="h-6 w-6 animate-spin fill-blue-600 text-gray-200 sm:h-12 sm:w-12"
|
||||
<Spinner
|
||||
outerFill="#d1d5db"
|
||||
className="h-6 w-6 sm:h-12 sm:w-12"
|
||||
innerFill="#2563eb"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -249,7 +248,7 @@ export function TopicDetail(props: TopicDetailProps) {
|
||||
setIsContributing(false);
|
||||
}}
|
||||
>
|
||||
<img alt="Close" className="h-5 w-5" src={CloseIcon.src} />
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -339,7 +338,7 @@ export function TopicDetail(props: TopicDetailProps) {
|
||||
setIsContributing(false);
|
||||
}}
|
||||
>
|
||||
<img alt="Close" className="h-5 w-5" src={CloseIcon.src} />
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
<div className="flex h-full flex-col items-center justify-center">
|
||||
<Ban className="h-16 w-16 text-red-500" />
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useKeydown } from '../../hooks/use-keydown';
|
||||
import { useOutsideClick } from '../../hooks/use-outside-click';
|
||||
import DownIcon from '../../icons/down.svg';
|
||||
import SpinnerIcon from '../../icons/spinner.svg';
|
||||
import { isLoggedIn } from '../../lib/jwt';
|
||||
import {
|
||||
getTopicStatus,
|
||||
@@ -10,9 +8,14 @@ import {
|
||||
renderTopicProgress,
|
||||
updateResourceProgress,
|
||||
} from '../../lib/resource-progress';
|
||||
import type { ResourceProgressType, ResourceType } from '../../lib/resource-progress';
|
||||
import type {
|
||||
ResourceProgressType,
|
||||
ResourceType,
|
||||
} from '../../lib/resource-progress';
|
||||
import { showLoginPopup } from '../../lib/popup';
|
||||
import { useToast } from '../../hooks/use-toast';
|
||||
import { Spinner } from '../ReactIcons/Spinner';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
|
||||
type TopicProgressButtonProps = {
|
||||
topicId: string;
|
||||
@@ -27,7 +30,7 @@ const statusColors: Record<ResourceProgressType, string> = {
|
||||
learning: 'bg-yellow-500',
|
||||
pending: 'bg-gray-300',
|
||||
skipped: 'bg-black',
|
||||
removed: ''
|
||||
removed: '',
|
||||
};
|
||||
|
||||
export function TopicProgressButton(props: TopicProgressButtonProps) {
|
||||
@@ -71,7 +74,7 @@ export function TopicProgressButton(props: TopicProgressButtonProps) {
|
||||
|
||||
handleUpdateResourceProgress('done');
|
||||
},
|
||||
[progress]
|
||||
[progress],
|
||||
);
|
||||
|
||||
// Mark as learning
|
||||
@@ -85,7 +88,7 @@ export function TopicProgressButton(props: TopicProgressButtonProps) {
|
||||
|
||||
handleUpdateResourceProgress('learning');
|
||||
},
|
||||
[progress]
|
||||
[progress],
|
||||
);
|
||||
|
||||
// Mark as learning
|
||||
@@ -99,7 +102,7 @@ export function TopicProgressButton(props: TopicProgressButtonProps) {
|
||||
|
||||
handleUpdateResourceProgress('skipped');
|
||||
},
|
||||
[progress]
|
||||
[progress],
|
||||
);
|
||||
|
||||
// Mark as pending
|
||||
@@ -114,7 +117,7 @@ export function TopicProgressButton(props: TopicProgressButtonProps) {
|
||||
|
||||
handleUpdateResourceProgress('pending');
|
||||
},
|
||||
[progress]
|
||||
[progress],
|
||||
);
|
||||
|
||||
const handleUpdateResourceProgress = (progress: ResourceProgressType) => {
|
||||
@@ -131,7 +134,7 @@ export function TopicProgressButton(props: TopicProgressButtonProps) {
|
||||
resourceId,
|
||||
resourceType,
|
||||
},
|
||||
progress
|
||||
progress,
|
||||
)
|
||||
.then(() => {
|
||||
setProgress(progress);
|
||||
@@ -149,22 +152,22 @@ export function TopicProgressButton(props: TopicProgressButtonProps) {
|
||||
};
|
||||
|
||||
const allowMarkingSkipped = ['pending', 'learning', 'done'].includes(
|
||||
progress
|
||||
progress,
|
||||
);
|
||||
const allowMarkingDone = ['skipped', 'pending', 'learning'].includes(
|
||||
progress
|
||||
progress,
|
||||
);
|
||||
const allowMarkingLearning = ['done', 'skipped', 'pending'].includes(
|
||||
progress
|
||||
progress,
|
||||
);
|
||||
const allowMarkingPending = ['skipped', 'done', 'learning'].includes(
|
||||
progress
|
||||
progress,
|
||||
);
|
||||
|
||||
if (isUpdatingProgress) {
|
||||
return (
|
||||
<button className="inline-flex cursor-default items-center rounded-md border border-gray-300 bg-white p-1 px-2 text-sm text-black">
|
||||
<img alt="Check" className="h-4 w-4 animate-spin" src={SpinnerIcon.src} />
|
||||
<Spinner className="h-4 w-4" />
|
||||
<span className="ml-2">Updating Status..</span>
|
||||
</button>
|
||||
);
|
||||
@@ -188,7 +191,7 @@ export function TopicProgressButton(props: TopicProgressButtonProps) {
|
||||
onClick={() => setShowChangeStatus(true)}
|
||||
>
|
||||
<span className="mr-0.5">Update Status</span>
|
||||
<img alt="Check" className="h-4 w-4" src={DownIcon.src} />
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
{showChangeStatus && (
|
||||
|
||||
@@ -4,13 +4,13 @@ import { useKeydown } from '../../hooks/use-keydown';
|
||||
import { httpGet } from '../../lib/http';
|
||||
import type { ResourceType } from '../../lib/resource-progress';
|
||||
import { topicSelectorAll } from '../../lib/resource-progress';
|
||||
import CloseIcon from '../../icons/close.svg';
|
||||
import { deleteUrlParam, getUrlParams } from '../../lib/browser';
|
||||
import { useAuth } from '../../hooks/use-auth';
|
||||
import type { GetRoadmapResponse } from '../CustomRoadmap/CustomRoadmap';
|
||||
import { ReadonlyEditor } from '../../../editor/readonly-editor';
|
||||
import { ProgressLoadingError } from './ProgressLoadingError';
|
||||
import { UserProgressModalHeader } from './UserProgressModalHeader';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
export type ProgressMapProps = {
|
||||
userId?: string;
|
||||
@@ -208,7 +208,7 @@ export function UserCustomProgressModal(props: ProgressMapProps) {
|
||||
className={`absolute right-2.5 top-3 ml-auto inline-flex items-center rounded-lg bg-gray-100 bg-transparent p-1.5 text-sm text-gray-400 hover:text-gray-900 lg:hidden`}
|
||||
onClick={onClose}
|
||||
>
|
||||
<img alt={'close'} src={CloseIcon.src} className="h-4 w-4" />
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close modal</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -6,11 +6,11 @@ import { useKeydown } from '../../hooks/use-keydown';
|
||||
import { httpGet } from '../../lib/http';
|
||||
import type { ResourceType } from '../../lib/resource-progress';
|
||||
import { topicSelectorAll } from '../../lib/resource-progress';
|
||||
import CloseIcon from '../../icons/close.svg';
|
||||
import { deleteUrlParam, getUrlParams } from '../../lib/browser';
|
||||
import { useAuth } from '../../hooks/use-auth';
|
||||
import { ProgressLoadingError } from './ProgressLoadingError';
|
||||
import { UserProgressModalHeader } from './UserProgressModalHeader';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
export type ProgressMapProps = {
|
||||
userId?: string;
|
||||
@@ -70,12 +70,12 @@ export function UserProgressModal(props: ProgressMapProps) {
|
||||
async function getUserProgress(
|
||||
userId: string,
|
||||
resourceType: string,
|
||||
resourceId: string
|
||||
resourceId: string,
|
||||
): Promise<UserProgressResponse | undefined> {
|
||||
const { error, response } = await httpGet<UserProgressResponse>(
|
||||
`${
|
||||
import.meta.env.PUBLIC_API_URL
|
||||
}/v1-get-user-progress/${userId}?resourceType=${resourceType}&resourceId=${resourceId}`
|
||||
}/v1-get-user-progress/${userId}?resourceType=${resourceType}&resourceId=${resourceId}`,
|
||||
);
|
||||
|
||||
if (error || !response) {
|
||||
@@ -86,7 +86,7 @@ export function UserProgressModal(props: ProgressMapProps) {
|
||||
}
|
||||
|
||||
async function getRoadmapSVG(
|
||||
jsonUrl: string
|
||||
jsonUrl: string,
|
||||
): Promise<SVGElement | undefined> {
|
||||
const { error, response: roadmapJson } = await httpGet(jsonUrl);
|
||||
if (error || !roadmapJson) {
|
||||
@@ -216,7 +216,7 @@ export function UserProgressModal(props: ProgressMapProps) {
|
||||
className={`absolute right-2.5 top-3 ml-auto inline-flex items-center rounded-lg bg-gray-100 bg-transparent p-1.5 text-sm text-gray-400 hover:text-gray-900 lg:hidden`}
|
||||
onClick={onClose}
|
||||
>
|
||||
<img alt={'close'} src={CloseIcon.src} className="h-4 w-4" />
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close modal</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
6246
src/data/roadmaps/devops/devops-forkable.json
Normal file
6246
src/data/roadmaps/devops/devops-forkable.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ briefDescription: 'Step by step guide for DevOps or operations role in 2023'
|
||||
title: 'DevOps Roadmap'
|
||||
description: 'Step by step guide for DevOps, SRE or any other Operations Role in 2023'
|
||||
hasTopics: true
|
||||
isForkable: true
|
||||
tnsBannerLink: 'https://thenewstack.io?utm_source=roadmap.sh&utm_medium=Referral&utm_campaign=Alert'
|
||||
question:
|
||||
title: 'What is DevOps?'
|
||||
|
||||
@@ -6,4 +6,4 @@ Visit the following resources to learn more:
|
||||
|
||||
- [Electron Website](https://www.electronjs.org/)
|
||||
- [Electron Docs](https://www.electronjs.org/docs/latest/)
|
||||
- [Create a Desktop App With JavaScript & Electron](https://youtu.be/ml743nrkmhw)
|
||||
- [Create a Desktop App With JavaScript & Electron](https://www.youtube.com/watch?v=ML743nrkMHw)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
# Note
|
||||
|
||||
These roadmaps cover everything that is there to learn for the paths listed below. Don't feel overwhelmed, you don't need to learn it all in the beginning if you are just getting started.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Server Side
|
||||
|
||||
The term "server-side" refers to operations that are performed by the server in a client-server relationship in a computer network. In games, the server-side is responsible for the logic and rules of the game, data persistence, security, and synchronization of the game state among all connected clients. It essentially handles all processing that isn't done on the client-side. Code run on the server-side can be written in various programming languages, but it's often in PHP, Ruby, Python, .NET, Java, or JavaScript (Node.js). Knowing server-side programming is critical for any game developer to create maintainable, scalable, and secure online games.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Checksum
|
||||
|
||||
A **checksum** in TCP (Transmission Control Protocol) is a 16-bit field in the TCP header used to perform error checks on the segments. TCP stack computes the checksum value for the data transmitted and sends it along with the data to the receiving system. The receiving system re-computes the checksum and compares it with the value sent along with the data. If both the computed values match, the data is assumed to be free from transmission errors. However, if the computed values don't match, TCP will detect a possible change in received data, and the receiver will request for the re-transmission of the lost or corrupted data packets. Please note that while the checksum operation helps to ensure data integrity, it is not entirely foolproof as it might not detect all possible errors, particularly those that involve multiple bit changes.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Segment Structure
|
||||
|
||||
TCP (Transmission Control Protocol) uses a method called "segmentation" to manage data transmission. In this approach, TCP divides a stream of data into smaller parts, known as "segments". Each of these segments is then independently sent over the internet. A basic TCP segment consists of a header and the data section. The header contains various fields such as source port, destination port, sequence number, acknowledgment number, data offset, reserved section, control bit, window, checksum, urgent pointer, and options. The rest of the segment is occupied by the data transferred by TCP. Understanding the structure of these segments is crucial for understanding how TCP, and thereby much of the Internet, operates.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Connection
|
||||
|
||||
`Connection` in server-side game development refers to the establishment of a link between the game server and the game client (usually the player's device). This connection can either be persistent or non-persistent, depending on the game’s requirements. Persistent connections remain active as long as the user is logged in, facilitating real-time communication between the server and the client. Non-persistent connections, on the other hand, are established and discontinued as needed. This element is crucial in multiplayer games where the server handles the synchronization of data among multiple clients, enabling players to interact with each other in the same virtual environment.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Resource Usage
|
||||
|
||||
In server-side game development, **resource usage** broadly refers to how a game uses the server's computational power, memory, storage, and networking capabilities. These resources are crucial in assuring overall game performance and responsiveness. When monitoring resource usage, you typically pay attention to CPU usage, memory utilization, storage capacity, network bandwidth, and database performance. Server-side code must be developed and optimized with these resources in mind, as inefficient code can lead to increased resource usage, potentially causing lag, disconnections, or crashes. Key elements like scalable architecture and serverless technologies are primarily employed to efficiently manage and balance resource usage. Tools and automated solutions are commonly utilized to monitor and manage these resources in real-time, allowing developers to identify potential issues and optimize accordingly.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Congestion Control
|
||||
|
||||
`Congestion Control` is a critical aspect of any network, particularly in server-side game development where real-time data transfer is a must. It refers to techniques and mechanisms that can prevent too much data from filling the network. This excessive data, also known as network congestion, can lead to packet loss, delay in data transfer, and ultimately a poor gaming experience. Congestion control algorithms, such as TCP congestion control algorithms, are implemented to avoid such situations. They adjust the data packet transmission rate based on the perceived network congestion, reducing the rate when congestion is detected and increasing it when the network is less congested.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Error Detection
|
||||
|
||||
Error detection is a mechanism that checks the integrity of data that is being delivered or stored. In the context of server-side game development, errors can occur due to reasons like network inconsistencies, corrupted data, or server overload. To ensure smooth operation, these errors need to be detected and handled. Some common types of error detection methods include parity check, checksum, and cyclic redundancy check (CRC). Parity check adds an extra bit to the data to make the number of 1’s either even or odd. Checksum involves the sender adding up all the bits in the data and sending the sum along with data. CRC is a more complex and robust method involving binary division. These methods help detect errors but can't fix them. Once detected, errors are typically dealt with through retransmission or by using forward error correction codes.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Flow Control
|
||||
|
||||
Flow control, often referred to as stream control transmission protocol (SCTP), is essential in server-side game development, particularly in data transfer. Flow control is basically a technique used to manage the rate of data transmission between two nodes to prevent a fast sender from overwhelming a slow receiver. It provides an effective way to manage the amount of data that can be sent before having to receive an acknowledgment. There are two main types of flow control: window-based and rate-based. Window-based flow control allows the receiver to process a fixed set of packets before sending the acknowledgement, whereas rate-based control allows for data transfer at a specified rate. These methods make flow control indispensable in network communications.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Data Transfer
|
||||
|
||||
Data transfer in server-side game development refers to the movement of data between the server and client or among various components of the server itself. Game data, such as player scores, game states, and updates, are frequently transferred to ensure a consistent and updated gaming environment. Various methods are used for data transfer, including RESTful APIs, websockets, and protocol buffers. The choice of method often depends on factors such as the size and type of data, the target platform, and the specific needs of the game. Remember, efficient and secure data transfer is essential for providing a smooth and engaging gaming experience.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Reliable Transmission
|
||||
|
||||
Reliable Transmission is a service that ensures data is delivered from the source to the destination without any errors. This includes mechanisms that provide accurate, on-time delivery of all packets while managing packet loss, duplication, or out-of-order delivery. In server-side game development, protocols like TCP (Transmission Control Protocol) are often used to accomplish reliable transmission. Reliable transmission is particularly necessary for critical data where all transmitted information must arrive at the destination correctly and in the right order. TCP accomplishes this by establishing a connection before data transfer begins and using sequence numbers to track delivery and reassemble pieces in the correct order. TCP also sends acknowledgements back to the source confirming packet delivery, and implements re-transmission of lost or corrupted data. Despite its overhead and slower speed compared to other methods, such as the User Datagram Protocol (UDP), reliable transmission is vital when data integrity is a priority over speed.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Max Segment Scaling
|
||||
|
||||
`Max Segment Scaling (MSS)` is a TCP feature that defines the maximum amount of data that can be received in a single TCP segment. It is specified during the TCP connection establishment phase. The MSS is calculated as the data link layer Maximum Transmission Unit (MTU) minus the size of the TCP and IP headers. The mechanism helps to avoid fragmentation at the IP layer, ensuring the data packets sent are optimal for the network path taken, preventing potential transmission inefficiencies or packet loss issues.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Window Scaling
|
||||
|
||||
Window Scaling is a mechanism in the Transmission Control Protocol (TCP) that provides support for larger receiver window sizes beyond the maximum limit of 65,535 bytes. This TCP feature is essential when dealing with high latency or high bandwidth networks (common in server-side game development), where frames might be significantly delayed or rapidly transmitted. The window size initially specified in the TCP header is augmented via a scale factor (defined during the connection setup), allowing the receiver window size to be as large as 1 gigabyte. However, keep in mind that Window Scaling can only be employed at the connection setup stage; once the connection is established, the scaling factor cannot be changed.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Timestamp
|
||||
|
||||
A `timestamp` in server side game development is essentially a sequence of characters, encoding the date and time at which a certain event occurred. This data is particularly significant when tracking the sequence of certain events, debugging issues, logging, and when performing any sort of data analysis. The timestamp is typically generated by the server and it reflects the server's current date and time. For instance, you might find it in a server log file, indicating when each event occurred, or in a database row, showing when each row was created or last updated. Timestamps are often included in the HTTP headers to let the client know when the resource was last modified.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Out-of-Band Data
|
||||
|
||||
"Out of band" data, in the context of server-side game development, refers to data that is transmitted separately from the main data stream. This data is used for managing control information rather than actual game data, for instance, data regarding the status of the server, notifications about issues, or urgent commands. Given its importance, it's often designed to bypass any queueing or buffering systems to be delivered straight to the application, hence its name — it is "out of band" compared to the normal data transmissions in the game. Please note, out of band data needs proper handling to prevent potential vulnerabilities including security issues.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Selective Acknowledgement
|
||||
|
||||
Selective Acknowledgement (SACK) is a mechanism introduced in TCP to improve its efficiency in handling packet loss in the network. When TCP detects packet loss, it normally retransmits all packets sent after the lost packet, regardless of whether they were received successfully or not. SACK, however, allows the receiver to acknowledge non-consecutive packets, effectively informing the sender exactly which packets were received successfully and which weren't. By using this mechanism, TCP can selectively retransmit only those packets that were lost, saving bandwidth and improving overall performance.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Operations
|
||||
|
||||
TCP (Transmission Control Protocol) is a connection-oriented protocol that ensures reliable, ordered, and error-checked delivery of data between systems over an IP network. Some of the primary operations include "Connection Establishment" that initializes a connection using a 3-way handshake, "Data Transfer" where packets of data are sent from sender to receiver, "Connection Termination" that closes the connection when transmission is complete, and "Error Control" which handles retransmission of lost data or corrupted packets. Additionally, TCP handles "Flow Control" to prevent a sender from overwhelming a receiver with data, and "Congestion Control" to manage network congestion and avoid packet loss.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Denial of Service
|
||||
|
||||
Denial of Service (DoS) is a malicious attempt to disrupt the regular functioning of a network, service, or server by overwhelming the network or server with a flood of internet traffic. The most common type of DoS attack involves flooding the target with unnecessary requests in an attempt to overload the system. In a Distributed Denial of Service (DDoS) attack, multiple computers are used to carry out the cyber attack. For servers, these attacks can significantly affect the availability and performance of games, causing a poor experience for the users.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Connection Hijacking
|
||||
|
||||
`Connection hijacking`, also known as session hijacking, is a serious security threat in the realm of server side game development. It refers to the exploitation of a valid computer session, or more precisely, the intrusion of an unauthorized user into a valid connection between two nodes or endpoints. The attacker intercepts the traffic between these two nodes, thereby 'hijacking' the connection. In game development, this could involve intercepting information between a game server and a client's system. Connection hijacking could expose sensitive data, tamper with the data in transit, or even redirect clients to rogue servers. Hence, implementing necessary security protocols to mitigate such vulnerability is crucial.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Veto
|
||||
|
||||
`Veto` is often used in multiplayer games to prevent or allow certain actions during the game. For instance, players can issue commands to block specific actions from their opponents. As a server-side game developer, you must ensure security measures are in place to validate the authenticity of these commands to safeguard against potential vulnerability. A veto vulnerability can occur when malicious players manipulate veto commands to their advantage or disrupt the game, which can lead to an unfair gaming environment or even crash the server. Therefore, your code should always verify who is sending veto commands and check the validity of these commands.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Vulnerability
|
||||
|
||||
`TCP` (Transmission Control Protocol) is an important protocol known for its reliability in delivering data packets, but it also has several vulnerabilities. The most significant weakness is TCP’s susceptibility to a variety of **Denial-of-Service (DoS)** attacks, such as TCP SYN flood attacks, where an attacker sends a succession of SYN requests to a target's system in an attempt to consume server resources to make the system unresponsive. Additionally, sequence prediction can be exploited where an attacker can predict the sequence number of TCP packets to hijack the TCP session. There are also risks of IP spoofing, where an attacker can forge the IP address in the TCP packet header and pretend to be another user in the network. These vulnerabilities may expose sensitive information, disrupt normal functions or allow unauthorized access, especially in the context of a server-side game developer dealing with potentially large volumes of traffic.
|
||||
@@ -0,0 +1,3 @@
|
||||
# TCP
|
||||
|
||||
TCP, an acronym for Transmission Control Protocol, is a fundamental protocol in the suite of internet protocols. It is a connection-oriented, reliable, byte stream service that sits at the transport layer of the OSI Model. In simpler terms, TCP enables two hosts to establish a connection and exchange data. During this communication, TCP ensures that data is not lost or received out of order by providing error checking mechanisms and a system for retransmission of lost data. Furthermore, to ensure efficient use of network resources, TCP implements flow control, congestion control and provides a means for hosts to dynamically adjust the throughput of the connection.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user