More frontend and I lost track
This commit is contained in:
parent
7255e22315
commit
11f84b9755
@ -11,11 +11,16 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^1.7.2",
|
"@headlessui/react": "^1.7.2",
|
||||||
"@heroicons/react": "^2.0.11",
|
"@heroicons/react": "^2.0.11",
|
||||||
|
"@hookform/resolvers": "^2.9.8",
|
||||||
|
"@tanstack/react-query": "^4.7.2",
|
||||||
|
"axios": "^0.27.2",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-hook-form": "^7.36.1",
|
||||||
"react-router-dom": "^6.4.1",
|
"react-router-dom": "^6.4.1",
|
||||||
"tailwind-scrollbar": "^2.0.1"
|
"tailwind-scrollbar": "^2.0.1",
|
||||||
|
"zod": "^3.19.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.0.17",
|
"@types/react": "^18.0.17",
|
||||||
@ -27,5 +32,6 @@
|
|||||||
"tailwindcss": "^3.1.8",
|
"tailwindcss": "^3.1.8",
|
||||||
"typescript": "^4.6.4",
|
"typescript": "^4.6.4",
|
||||||
"vite": "^3.1.0"
|
"vite": "^3.1.0"
|
||||||
}
|
},
|
||||||
|
"proxy": "http://localhost:5000"
|
||||||
}
|
}
|
117
client/pnpm-lock.yaml
generated
117
client/pnpm-lock.yaml
generated
@ -3,29 +3,39 @@ lockfileVersion: 5.4
|
|||||||
specifiers:
|
specifiers:
|
||||||
'@headlessui/react': ^1.7.2
|
'@headlessui/react': ^1.7.2
|
||||||
'@heroicons/react': ^2.0.11
|
'@heroicons/react': ^2.0.11
|
||||||
|
'@hookform/resolvers': ^2.9.8
|
||||||
|
'@tanstack/react-query': ^4.7.2
|
||||||
'@types/react': ^18.0.17
|
'@types/react': ^18.0.17
|
||||||
'@types/react-dom': ^18.0.6
|
'@types/react-dom': ^18.0.6
|
||||||
'@vitejs/plugin-react': ^2.1.0
|
'@vitejs/plugin-react': ^2.1.0
|
||||||
autoprefixer: ^10.4.12
|
autoprefixer: ^10.4.12
|
||||||
|
axios: ^0.27.2
|
||||||
clsx: ^1.2.1
|
clsx: ^1.2.1
|
||||||
postcss: ^8.4.16
|
postcss: ^8.4.16
|
||||||
prettier: ^2.7.1
|
prettier: ^2.7.1
|
||||||
react: ^18.2.0
|
react: ^18.2.0
|
||||||
react-dom: ^18.2.0
|
react-dom: ^18.2.0
|
||||||
|
react-hook-form: ^7.36.1
|
||||||
react-router-dom: ^6.4.1
|
react-router-dom: ^6.4.1
|
||||||
tailwind-scrollbar: ^2.0.1
|
tailwind-scrollbar: ^2.0.1
|
||||||
tailwindcss: ^3.1.8
|
tailwindcss: ^3.1.8
|
||||||
typescript: ^4.6.4
|
typescript: ^4.6.4
|
||||||
vite: ^3.1.0
|
vite: ^3.1.0
|
||||||
|
zod: ^3.19.1
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@headlessui/react': 1.7.2_biqbaboplfbrettd7655fr4n2y
|
'@headlessui/react': 1.7.2_biqbaboplfbrettd7655fr4n2y
|
||||||
'@heroicons/react': 2.0.11_react@18.2.0
|
'@heroicons/react': 2.0.11_react@18.2.0
|
||||||
|
'@hookform/resolvers': 2.9.8_react-hook-form@7.36.1
|
||||||
|
'@tanstack/react-query': 4.7.2_biqbaboplfbrettd7655fr4n2y
|
||||||
|
axios: 0.27.2
|
||||||
clsx: 1.2.1
|
clsx: 1.2.1
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
|
react-hook-form: 7.36.1_react@18.2.0
|
||||||
react-router-dom: 6.4.1_biqbaboplfbrettd7655fr4n2y
|
react-router-dom: 6.4.1_biqbaboplfbrettd7655fr4n2y
|
||||||
tailwind-scrollbar: 2.0.1_tailwindcss@3.1.8
|
tailwind-scrollbar: 2.0.1_tailwindcss@3.1.8
|
||||||
|
zod: 3.19.1
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/react': 18.0.21
|
'@types/react': 18.0.21
|
||||||
@ -344,6 +354,14 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@hookform/resolvers/2.9.8_react-hook-form@7.36.1:
|
||||||
|
resolution: {integrity: sha512-iVVjH0USq+1TqDdGkWe2M1x7Wn5OFPgVRo7CbWFsXTqqXqCaZtZcnzJu+UhljCWbthFWxWGXKLGYUDPZ04oVvQ==}
|
||||||
|
peerDependencies:
|
||||||
|
react-hook-form: ^7.0.0
|
||||||
|
dependencies:
|
||||||
|
react-hook-form: 7.36.1_react@18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@jridgewell/gen-mapping/0.1.1:
|
/@jridgewell/gen-mapping/0.1.1:
|
||||||
resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==}
|
resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
@ -405,6 +423,28 @@ packages:
|
|||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@tanstack/query-core/4.7.2:
|
||||||
|
resolution: {integrity: sha512-1zQuFsKShMhLY6rQYBEmkYiK9Zcb3lQcCVOTIgQcvliKIxPyZFaE/8LMtaITEEfgGF5qwYqHdm61+BUtpyNsrg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@tanstack/react-query/4.7.2_biqbaboplfbrettd7655fr4n2y:
|
||||||
|
resolution: {integrity: sha512-4nJ0HxU2kxkaHZ/swJw39io3Bb3liiggJBsCCdFOydZOl8AJDRCor1E3GsOBrtn53HT01R9EIP4PY/6fyYdKsw==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
react-native: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
react-dom:
|
||||||
|
optional: true
|
||||||
|
react-native:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@tanstack/query-core': 4.7.2
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0_react@18.2.0
|
||||||
|
use-sync-external-store: 1.2.0_react@18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/prop-types/15.7.5:
|
/@types/prop-types/15.7.5:
|
||||||
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
|
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -478,6 +518,10 @@ packages:
|
|||||||
/arg/5.0.2:
|
/arg/5.0.2:
|
||||||
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
|
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
|
||||||
|
|
||||||
|
/asynckit/0.4.0:
|
||||||
|
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/autoprefixer/10.4.12_postcss@8.4.16:
|
/autoprefixer/10.4.12_postcss@8.4.16:
|
||||||
resolution: {integrity: sha512-WrCGV9/b97Pa+jtwf5UGaRjgQIg7OK3D06GnoYoZNcG1Xb8Gt3EfuKjlhh9i/VtT16g6PYjZ69jdJ2g8FxSC4Q==}
|
resolution: {integrity: sha512-WrCGV9/b97Pa+jtwf5UGaRjgQIg7OK3D06GnoYoZNcG1Xb8Gt3EfuKjlhh9i/VtT16g6PYjZ69jdJ2g8FxSC4Q==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
@ -494,6 +538,15 @@ packages:
|
|||||||
postcss-value-parser: 4.2.0
|
postcss-value-parser: 4.2.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/axios/0.27.2:
|
||||||
|
resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
|
||||||
|
dependencies:
|
||||||
|
follow-redirects: 1.15.2
|
||||||
|
form-data: 4.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- debug
|
||||||
|
dev: false
|
||||||
|
|
||||||
/binary-extensions/2.2.0:
|
/binary-extensions/2.2.0:
|
||||||
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
|
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -564,6 +617,13 @@ packages:
|
|||||||
/color-name/1.1.4:
|
/color-name/1.1.4:
|
||||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||||
|
|
||||||
|
/combined-stream/1.0.8:
|
||||||
|
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
dependencies:
|
||||||
|
delayed-stream: 1.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/convert-source-map/1.8.0:
|
/convert-source-map/1.8.0:
|
||||||
resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==}
|
resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -594,6 +654,11 @@ packages:
|
|||||||
/defined/1.0.0:
|
/defined/1.0.0:
|
||||||
resolution: {integrity: sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==}
|
resolution: {integrity: sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==}
|
||||||
|
|
||||||
|
/delayed-stream/1.0.0:
|
||||||
|
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/detective/5.2.1:
|
/detective/5.2.1:
|
||||||
resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==}
|
resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==}
|
||||||
engines: {node: '>=0.8.0'}
|
engines: {node: '>=0.8.0'}
|
||||||
@ -854,6 +919,25 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
to-regex-range: 5.0.1
|
to-regex-range: 5.0.1
|
||||||
|
|
||||||
|
/follow-redirects/1.15.2:
|
||||||
|
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
|
||||||
|
engines: {node: '>=4.0'}
|
||||||
|
peerDependencies:
|
||||||
|
debug: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
debug:
|
||||||
|
optional: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/form-data/4.0.0:
|
||||||
|
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
dependencies:
|
||||||
|
asynckit: 0.4.0
|
||||||
|
combined-stream: 1.0.8
|
||||||
|
mime-types: 2.1.35
|
||||||
|
dev: false
|
||||||
|
|
||||||
/fraction.js/4.2.0:
|
/fraction.js/4.2.0:
|
||||||
resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
|
resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -970,6 +1054,18 @@ packages:
|
|||||||
braces: 3.0.2
|
braces: 3.0.2
|
||||||
picomatch: 2.3.1
|
picomatch: 2.3.1
|
||||||
|
|
||||||
|
/mime-db/1.52.0:
|
||||||
|
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/mime-types/2.1.35:
|
||||||
|
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dependencies:
|
||||||
|
mime-db: 1.52.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/minimist/1.2.6:
|
/minimist/1.2.6:
|
||||||
resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==}
|
resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==}
|
||||||
|
|
||||||
@ -1099,6 +1195,15 @@ packages:
|
|||||||
scheduler: 0.23.0
|
scheduler: 0.23.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/react-hook-form/7.36.1_react@18.2.0:
|
||||||
|
resolution: {integrity: sha512-EbYYkCG2p8ywe7ikOH2l02lAFMrrrslZi1I8fqd8ifDGNAkhomHZQzQsP6ksvzrWBKntRe8b5L5L7Zsd+Gm02Q==}
|
||||||
|
engines: {node: '>=12.22.0'}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17 || ^18
|
||||||
|
dependencies:
|
||||||
|
react: 18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-refresh/0.14.0:
|
/react-refresh/0.14.0:
|
||||||
resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==}
|
resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -1271,6 +1376,14 @@ packages:
|
|||||||
picocolors: 1.0.0
|
picocolors: 1.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/use-sync-external-store/1.2.0_react@18.2.0:
|
||||||
|
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
dependencies:
|
||||||
|
react: 18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/util-deprecate/1.0.2:
|
/util-deprecate/1.0.2:
|
||||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
|
|
||||||
@ -1308,3 +1421,7 @@ packages:
|
|||||||
/yaml/1.10.2:
|
/yaml/1.10.2:
|
||||||
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
|
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
|
/zod/3.19.1:
|
||||||
|
resolution: {integrity: sha512-LYjZsEDhCdYET9ikFu6dVPGp2YH9DegXjdJToSzD9rO6fy4qiRYFoyEYwps88OseJlPyl2NOe2iJuhEhL7IpEA==}
|
||||||
|
dev: false
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Routes, Route } from "react-router-dom";
|
import { Routes, Route } from "react-router-dom";
|
||||||
import BrowseLayout from "./components/BrowseLayout";
|
import BrowseLayout from "./components/BrowseLayout";
|
||||||
|
import CategoryPage from "./pages/CategoryPage";
|
||||||
import ChannelPage from "./pages/ChannelPage";
|
import ChannelPage from "./pages/ChannelPage";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
@ -7,6 +8,7 @@ function App() {
|
|||||||
<Routes>
|
<Routes>
|
||||||
<Route element={<BrowseLayout />}>
|
<Route element={<BrowseLayout />}>
|
||||||
<Route path="/:channel" element={<ChannelPage />} />
|
<Route path="/:channel" element={<ChannelPage />} />
|
||||||
|
<Route path="/category/:category" element={<CategoryPage />} />
|
||||||
<Route path="/" element={<h1>Hi</h1>} />
|
<Route path="/" element={<h1>Hi</h1>} />
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
|
@ -18,11 +18,7 @@ const getStyling = (variant?: ButtonVariants) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const Button: FC<ButtonProps> = ({
|
const Button: FC<ButtonProps> = ({ className, variant, ...rest }) => {
|
||||||
className,
|
|
||||||
variant,
|
|
||||||
...rest
|
|
||||||
}: ButtonProps) => {
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={clsx("rounded-md", getStyling(variant), className)}
|
className={clsx("rounded-md", getStyling(variant), className)}
|
||||||
|
@ -2,7 +2,7 @@ import { FC } from "react";
|
|||||||
|
|
||||||
const ChatBadge: FC = () => {
|
const ChatBadge: FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className="w-5 h-5 rounded-sm bg-pink-300 inline-block align-middle" />
|
<span className="w-5 h-5 rounded-sm bg-pink-300 inline-block align-middle" />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ReactNode } from "react";
|
import { FC, forwardRef, ReactNode } from "react";
|
||||||
import Input from "./Input";
|
import Input from "./Input";
|
||||||
|
|
||||||
interface FormFieldProps extends React.ComponentPropsWithoutRef<"input"> {
|
interface FormFieldProps extends React.ComponentPropsWithoutRef<"input"> {
|
||||||
@ -6,17 +6,19 @@ interface FormFieldProps extends React.ComponentPropsWithoutRef<"input"> {
|
|||||||
bottomElement?: ReactNode;
|
bottomElement?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FormField = ({ label, bottomElement, ...inputProps }: FormFieldProps) => {
|
const FormField = forwardRef<HTMLInputElement, FormFieldProps>(
|
||||||
return (
|
({ label, bottomElement, ...inputProps }, ref) => {
|
||||||
<div className="space-y-1">
|
return (
|
||||||
<label htmlFor={inputProps.id} className="font-semibold text-sm">
|
<div className="space-y-1">
|
||||||
{label}
|
<label htmlFor={inputProps.id} className="font-semibold text-sm">
|
||||||
</label>
|
{label}
|
||||||
<br />
|
</label>
|
||||||
<Input {...inputProps} />
|
<br />
|
||||||
{bottomElement}
|
<Input {...inputProps} ref={ref} />
|
||||||
</div>
|
{bottomElement}
|
||||||
);
|
</div>
|
||||||
};
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export default FormField;
|
export default FormField;
|
||||||
|
@ -1,17 +1,21 @@
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import { forwardRef } from "react";
|
||||||
|
|
||||||
interface InputProps extends React.ComponentPropsWithoutRef<"input"> {}
|
interface InputProps extends React.ComponentPropsWithoutRef<"input"> {}
|
||||||
|
|
||||||
const Input = ({ className, ...rest }: InputProps) => {
|
const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||||
return (
|
({ className, ...rest }, ref) => {
|
||||||
<input
|
return (
|
||||||
className={clsx(
|
<input
|
||||||
"bg-zinc-700 rounded-md box-border focus:outline outline-violet-400 text-sm",
|
className={clsx(
|
||||||
className
|
"bg-zinc-700 rounded-md box-border focus:outline outline-violet-400 text-sm",
|
||||||
)}
|
className
|
||||||
{...rest}
|
)}
|
||||||
/>
|
{...rest}
|
||||||
);
|
ref={ref}
|
||||||
};
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export default Input;
|
export default Input;
|
||||||
|
41
client/src/components/LoginForm.tsx
Normal file
41
client/src/components/LoginForm.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { FC } from "react";
|
||||||
|
import { useForm, SubmitHandler } from "react-hook-form";
|
||||||
|
|
||||||
|
import FormField from "./FormField";
|
||||||
|
import InlineLink from "./InlineLink";
|
||||||
|
import SubmitButton from "./SubmitButton";
|
||||||
|
|
||||||
|
interface LoginFormValues {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LoginForm: FC = () => {
|
||||||
|
const { register, handleSubmit } = useForm<LoginFormValues>();
|
||||||
|
const onSubmit: SubmitHandler<LoginFormValues> = (data) => console.log(data);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||||
|
<FormField
|
||||||
|
label="Username"
|
||||||
|
className="py-2 px-2 outline-2 w-full"
|
||||||
|
{...register("username")}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
label="Password"
|
||||||
|
type="password"
|
||||||
|
{...register("password")}
|
||||||
|
className="py-2 px-2 outline-2 w-full"
|
||||||
|
bottomElement={
|
||||||
|
<InlineLink to="#" className="block mt-2">
|
||||||
|
Trouble logging in?
|
||||||
|
</InlineLink>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<SubmitButton className="w-full" value="Log In" />
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoginForm;
|
@ -1,12 +1,11 @@
|
|||||||
import React, { FC } from "react";
|
import { FC } from "react";
|
||||||
import { Dialog } from "@headlessui/react";
|
import { Dialog } from "@headlessui/react";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import { Tab } from "@headlessui/react";
|
import { Tab } from "@headlessui/react";
|
||||||
import clsx from "clsx";
|
|
||||||
import FormField from "./FormField";
|
|
||||||
import Button from "./Button";
|
|
||||||
import InlineLink from "./InlineLink";
|
|
||||||
import logo from "../assets/images/logo.png";
|
import logo from "../assets/images/logo.png";
|
||||||
|
import LoginForm from "./LoginForm";
|
||||||
|
import LoginModalTab from "./LoginModalTab";
|
||||||
|
import SignupForm from "./SignupForm";
|
||||||
|
|
||||||
export interface LoginModelProps {
|
export interface LoginModelProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -39,67 +38,11 @@ const LoginModal: FC<LoginModelProps> = ({ defaultPage, isOpen, onClose }) => {
|
|||||||
</Tab>
|
</Tab>
|
||||||
</Tab.List>
|
</Tab.List>
|
||||||
<Tab.Panels className="mt-4">
|
<Tab.Panels className="mt-4">
|
||||||
<Tab.Panel className="space-y-4">
|
<Tab.Panel>
|
||||||
<FormField
|
<LoginForm />
|
||||||
id="login-username"
|
|
||||||
label="Username"
|
|
||||||
className="py-2 px-2 outline-2 w-full"
|
|
||||||
autoFocus
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
id="login-password"
|
|
||||||
label="Password"
|
|
||||||
type="password"
|
|
||||||
className="py-2 px-2 outline-2 w-full"
|
|
||||||
bottomElement={
|
|
||||||
<InlineLink to="#" className="block mt-2">
|
|
||||||
Trouble logging in?
|
|
||||||
</InlineLink>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Button className="bg-violet-500 w-full font-semibold py-2 text-sm">
|
|
||||||
Log In
|
|
||||||
</Button>
|
|
||||||
</Tab.Panel>
|
</Tab.Panel>
|
||||||
<Tab.Panel className="space-y-4">
|
<Tab.Panel>
|
||||||
<p className="text-sm">
|
<SignupForm />
|
||||||
Creating an account allows you to participate in chat, follow
|
|
||||||
your favorite channels, and broadcast from your own channel.
|
|
||||||
</p>
|
|
||||||
<FormField
|
|
||||||
id="signup-username"
|
|
||||||
label="Username"
|
|
||||||
className="py-2 px-2 outline-2 w-full"
|
|
||||||
autoFocus
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
id="signup-password"
|
|
||||||
label="Password"
|
|
||||||
type="password"
|
|
||||||
className="py-2 px-2 outline-2 w-full"
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
id="signup-confirm-password"
|
|
||||||
label="Confirm Password"
|
|
||||||
type="password"
|
|
||||||
className="py-2 px-2 outline-2 w-full"
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
id="signup-email"
|
|
||||||
label="Email"
|
|
||||||
type="email"
|
|
||||||
className="py-2 px-2 outline-2 w-full"
|
|
||||||
/>
|
|
||||||
<p className="text-sm text-center">
|
|
||||||
By clicking Sign Up, you are agreeing to twitch-clone's{" "}
|
|
||||||
<InlineLink to="https://tosdr.org/en/service/200" external>
|
|
||||||
Terms of Service
|
|
||||||
</InlineLink>
|
|
||||||
.
|
|
||||||
</p>
|
|
||||||
<Button className="bg-violet-500 w-full font-semibold py-2 text-sm">
|
|
||||||
Sign Up
|
|
||||||
</Button>
|
|
||||||
</Tab.Panel>
|
</Tab.Panel>
|
||||||
</Tab.Panels>
|
</Tab.Panels>
|
||||||
</Tab.Group>
|
</Tab.Group>
|
||||||
@ -110,20 +53,4 @@ const LoginModal: FC<LoginModelProps> = ({ defaultPage, isOpen, onClose }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface LoginModalTabProps extends React.ComponentPropsWithoutRef<"p"> {
|
|
||||||
selected: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const LoginModalTab: FC<LoginModalTabProps> = ({ selected, ...rest }) => {
|
|
||||||
return (
|
|
||||||
<p
|
|
||||||
className={clsx(
|
|
||||||
"font-semibold p-1",
|
|
||||||
selected && "text-violet-400 border-b-2 border-b-violet-400"
|
|
||||||
)}
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LoginModal;
|
export default LoginModal;
|
||||||
|
20
client/src/components/LoginModalTab.tsx
Normal file
20
client/src/components/LoginModalTab.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { FC } from "react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
interface LoginModalTabProps extends React.ComponentPropsWithoutRef<"p"> {
|
||||||
|
selected: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LoginModalTab: FC<LoginModalTabProps> = ({ selected, ...rest }) => {
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
className={clsx(
|
||||||
|
"font-semibold p-1",
|
||||||
|
selected && "text-violet-400 border-b-2 border-b-violet-400"
|
||||||
|
)}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoginModalTab;
|
@ -20,7 +20,7 @@ const NavBar: FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="bg-zinc-800 w-screen font-semibold">
|
<nav className="bg-zinc-800 w-screen font-semibold border-b border-b-black">
|
||||||
<div className="flex flex-row justify-between items-center mx-2">
|
<div className="flex flex-row justify-between items-center mx-2">
|
||||||
<div className="basis-1/4">
|
<div className="basis-1/4">
|
||||||
<ul className="flex flex-row space-x-8 items-center">
|
<ul className="flex flex-row space-x-8 items-center">
|
||||||
|
@ -6,16 +6,10 @@ interface SideNavChannelProps {
|
|||||||
stream: Stream;
|
stream: Stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SideNavChannel: FC<SideNavChannelProps> = ({
|
const SideNavChannel: FC<SideNavChannelProps> = ({ stream }) => {
|
||||||
stream,
|
|
||||||
}: SideNavChannelProps) => {
|
|
||||||
const imgSrc = stream.thumbnail_url
|
|
||||||
.replace("{width}", "150")
|
|
||||||
.replace("{height}", "150");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row px-3 py-2 text-sm leading-4 space-x-2 hover:bg-neutral-700/40 cursor-pointer">
|
<div className="flex flex-row px-3 py-2 text-sm leading-4 space-x-2 hover:bg-neutral-700/40 cursor-pointer">
|
||||||
<img className="rounded-full w-8 h-8" src={imgSrc} />
|
<img className="rounded-full w-8 h-8" src={stream.thumbnail_url} />
|
||||||
<div className="flex flex-col flex-1">
|
<div className="flex flex-col flex-1">
|
||||||
<div className="flex flex-row justify-between">
|
<div className="flex flex-row justify-between">
|
||||||
<div className="font-bold">{stream.user_name}</div>
|
<div className="font-bold">{stream.user_name}</div>
|
||||||
|
95
client/src/components/SignupForm.tsx
Normal file
95
client/src/components/SignupForm.tsx
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { FC } from "react";
|
||||||
|
import { SubmitHandler, useForm } from "react-hook-form";
|
||||||
|
import FormField from "./FormField";
|
||||||
|
import InlineLink from "./InlineLink";
|
||||||
|
import SubmitButton from "./SubmitButton";
|
||||||
|
import axios from "axios";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
|
||||||
|
const PASSWORD_REGEX =
|
||||||
|
/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,64}$/;
|
||||||
|
|
||||||
|
const SignupFormSchema = z
|
||||||
|
.object({
|
||||||
|
username: z.string().trim().min(1).max(16),
|
||||||
|
password: z.string().trim().regex(PASSWORD_REGEX),
|
||||||
|
passwordRepeat: z.string().trim(),
|
||||||
|
email: z.string().email().trim(),
|
||||||
|
})
|
||||||
|
.refine((data) => data.password === data.passwordRepeat, {
|
||||||
|
message: "Passwords don't match",
|
||||||
|
path: ["passwordRepeat"],
|
||||||
|
});
|
||||||
|
|
||||||
|
type SignupFormValues = z.infer<typeof SignupFormSchema>;
|
||||||
|
|
||||||
|
const SignupForm: FC = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const signUp = useMutation(
|
||||||
|
({ username, password, email }: SignupFormValues) => {
|
||||||
|
return axios.post<{ access_token: string }>("/auth/signup", {
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
email,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: (resp) => {
|
||||||
|
// TODO: store access token as HTTP-Only cookie
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { register, handleSubmit } = useForm<SignupFormValues>({
|
||||||
|
resolver: zodResolver(SignupFormSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit: SubmitHandler<SignupFormValues> = (data) => {
|
||||||
|
signUp.mutate(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||||
|
<p className="text-sm">
|
||||||
|
Creating an account allows you to participate in chat, follow your
|
||||||
|
favorite channels, and broadcast from your own channel.
|
||||||
|
</p>
|
||||||
|
<FormField
|
||||||
|
label="Username"
|
||||||
|
{...register("username")}
|
||||||
|
className="py-2 px-2 outline-2 w-full"
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
label="Password"
|
||||||
|
{...register("password")}
|
||||||
|
type="password"
|
||||||
|
className="py-2 px-2 outline-2 w-full"
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
label="Confirm Password"
|
||||||
|
{...register("passwordRepeat")}
|
||||||
|
type="password"
|
||||||
|
className="py-2 px-2 outline-2 w-full"
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
label="Email"
|
||||||
|
{...register("email")}
|
||||||
|
type="email"
|
||||||
|
className="py-2 px-2 outline-2 w-full"
|
||||||
|
/>
|
||||||
|
<p className="text-sm text-center">
|
||||||
|
By clicking Sign Up, you are agreeing to twitch-clone's{" "}
|
||||||
|
<InlineLink to="https://tosdr.org/en/service/200" external>
|
||||||
|
Terms of Service
|
||||||
|
</InlineLink>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
<SubmitButton className="w-full" value="Sign Up" />
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SignupForm;
|
19
client/src/components/SubmitButton.tsx
Normal file
19
client/src/components/SubmitButton.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { FC } from "react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
interface ButtonProps extends React.ComponentPropsWithoutRef<"input"> {}
|
||||||
|
|
||||||
|
const SubmitButton: FC<ButtonProps> = ({ className, ...rest }) => {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
className={clsx(
|
||||||
|
"rounded-md bg-violet-500 font-semibold py-2 text-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SubmitButton;
|
@ -1,13 +1,18 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import { BrowserRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import "./styles/global.css";
|
import "./styles/global.css";
|
||||||
|
|
||||||
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<App />
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<App />
|
||||||
|
</QueryClientProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
26
client/src/pages/CategoryPage.tsx
Normal file
26
client/src/pages/CategoryPage.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { categories } from "../placeholder/SearchCategories";
|
||||||
|
|
||||||
|
function ChannelPage() {
|
||||||
|
const category = categories.data[0];
|
||||||
|
return (
|
||||||
|
<div className="flex-1 flex flex-row">
|
||||||
|
<div className="bg-neutral-900 flex-1 text-gray-100">
|
||||||
|
<div className="max-w-[200rem] mx-12 mt-12">
|
||||||
|
<div className="flex flex-row items-center space-x-4">
|
||||||
|
<img src={category.box_art_url} />
|
||||||
|
<div className="">
|
||||||
|
<h1>{category.name}</h1>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<span>603K</span> Viewers * <span>20.8M</span> Followers
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChannelPage;
|
@ -52,8 +52,8 @@ function ChannelPage() {
|
|||||||
<div className="w-5" />
|
<div className="w-5" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-scrollbar">
|
<div className="flex-1 overflow-scrollbar">
|
||||||
{new Array(60).fill(0).map(() => (
|
{new Array(60).fill(0).map((_, i) => (
|
||||||
<ChatMessage />
|
<ChatMessage key={i} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="m-2">
|
<div className="m-2">
|
||||||
|
@ -15,7 +15,7 @@ const streams: FollowedStreams = {
|
|||||||
started_at: "2021-03-10T15:04:21Z",
|
started_at: "2021-03-10T15:04:21Z",
|
||||||
language: "es",
|
language: "es",
|
||||||
thumbnail_url:
|
thumbnail_url:
|
||||||
"https://static-cdn.jtvnw.net/previews-ttv/live_user_auronplay-{width}x{height}.jpg",
|
"https://static-cdn.jtvnw.net/previews-ttv/live_user_auronplay-250x250.jpg",
|
||||||
tag_ids: [],
|
tag_ids: [],
|
||||||
is_mature: false,
|
is_mature: false,
|
||||||
},
|
},
|
||||||
@ -32,7 +32,7 @@ const streams: FollowedStreams = {
|
|||||||
started_at: "2022-09-29T14:04:21Z",
|
started_at: "2022-09-29T14:04:21Z",
|
||||||
language: "en",
|
language: "en",
|
||||||
thumbnail_url:
|
thumbnail_url:
|
||||||
"https://static-cdn.jtvnw.net/previews-ttv/live_user_xqcow-{width}x{height}.jpg",
|
"https://static-cdn.jtvnw.net/previews-ttv/live_user_xqcow-250x250.jpg",
|
||||||
tag_ids: [],
|
tag_ids: [],
|
||||||
is_mature: false,
|
is_mature: false,
|
||||||
},
|
},
|
||||||
|
12
client/src/placeholder/SearchCategories.ts
Normal file
12
client/src/placeholder/SearchCategories.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export const categories = {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
id: "33214",
|
||||||
|
name: "Just Chatting",
|
||||||
|
box_art_url: "https://static-cdn.jtvnw.net/ttv-boxart/509658-144x192.jpg",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pagination: {
|
||||||
|
cursor: "eyJiIjpudWxsLCJhIjp7IkN",
|
||||||
|
},
|
||||||
|
};
|
@ -39,3 +39,14 @@ export interface FollowedStreams {
|
|||||||
data: Stream[];
|
data: Stream[];
|
||||||
pagination: Pagination;
|
pagination: Pagination;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Category {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
box_art_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchCategories {
|
||||||
|
data: Category[];
|
||||||
|
pagination: Pagination;
|
||||||
|
}
|
||||||
|
2
go.mod
2
go.mod
@ -13,7 +13,7 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||||
github.com/bwmarrin/snowflake v0.3.0 // indirect
|
github.com/bwmarrin/snowflake v0.3.0 // direct
|
||||||
github.com/go-playground/locales v0.14.0 // indirect
|
github.com/go-playground/locales v0.14.0 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||||
|
Loading…
x
Reference in New Issue
Block a user