mirror of
https://github.com/emailerfacu-spec/minix-front.git
synced 2026-04-02 13:20:43 -03:00
Compare commits
42 Commits
firebase-O
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b4800d0d9 | |||
| 022623e22c | |||
| c96df5af92 | |||
|
|
d278c75688 | ||
| 3e07252c6f | |||
| ec9ec1f58a | |||
|
|
4ca434da9e | ||
|
|
1c4140e0a1 | ||
|
|
cdc9e74095 | ||
| 8168ceb00d | |||
|
|
1070b71f1f | ||
|
|
70093ce186 | ||
| 1da53348d0 | |||
| 9546b6548d | |||
| 52c583a039 | |||
| 7b91483514 | |||
| 85477ffe7e | |||
| 826d6478d7 | |||
| 5b5f82c0d6 | |||
| dc9c3b4f3c | |||
|
|
93c4b15d5b | ||
| 7901a677d5 | |||
| 0ea873e039 | |||
| c0d637a34c | |||
| e1864660a7 | |||
| 37f1b46306 | |||
| 1f4166a651 | |||
| b5ba2437b4 | |||
| ceec19fb91 | |||
| 9c59c3d036 | |||
| 921836c115 | |||
|
|
54593dd2eb | ||
|
|
9eb92b0c06 | ||
| 1c79c5fcda | |||
|
|
000c65712f | ||
|
|
d35de05a7b | ||
|
|
0ba88d14ed | ||
| 65d65baaee | |||
| e4508a0487 | |||
| 6cf920799c | |||
| ebc2ebc322 | |||
| 8fbe62d391 |
@@ -1,4 +1,5 @@
|
|||||||
# Package Managers
|
# Package Managers
|
||||||
|
src/lib/components/ui/
|
||||||
package-lock.json
|
package-lock.json
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
yarn.lock
|
yarn.lock
|
||||||
|
|||||||
@@ -3,10 +3,7 @@
|
|||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "none",
|
"trailingComma": "none",
|
||||||
"printWidth": 100,
|
"printWidth": 100,
|
||||||
"plugins": [
|
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||||
"prettier-plugin-svelte",
|
|
||||||
"prettier-plugin-tailwindcss"
|
|
||||||
],
|
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": "*.svelte",
|
"files": "*.svelte",
|
||||||
|
|||||||
53
README.md
53
README.md
@@ -1,38 +1,23 @@
|
|||||||
# sv
|
# Minix - Front
|
||||||
|
Este repositorio consiste del repo que contiene el codigo para poder hacer deploy de una instancia de minix
|
||||||
|
|
||||||
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
|
# ¿Que es Minix?
|
||||||
|
Intentamos hacer algo parecido a x.com pero adaptado a nuestra vision.
|
||||||
|
|
||||||
## Creating a project
|
# Galeria
|
||||||
|
<img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/2ebe2983-04dc-4cca-ab46-e361ec6f72ee" />
|
||||||
|
<img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/49529256-2c36-4a40-bbab-d02228028def" />
|
||||||
|
<img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/38045721-c350-4e06-a5e1-4aa271139894" />
|
||||||
|
<img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/5c228110-feac-4d52-8451-222f04034f94" />
|
||||||
|
<img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/9618162d-fcde-42ff-bd7e-fe334498e9c7" />
|
||||||
|
<img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/580f0ad3-89ed-4686-98c2-f8dea04f80de" />
|
||||||
|
<img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/ce40fd1a-bac0-443f-b01e-d861c335236c" />
|
||||||
|
<img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/ea24be47-f35a-4d68-a9d6-f6796e656911" />
|
||||||
|
<img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/adf1f792-31e0-4f10-a4f3-e8590b4f0582" />
|
||||||
|
|
||||||
If you're seeing this, you've probably already done this step. Congrats!
|
# ¿Que tecnologias usamos?
|
||||||
|
- svelte(kit) (framework)
|
||||||
|
- shadcn-svelte (ui)
|
||||||
|
- firebase/auth
|
||||||
|
- vercel (host)
|
||||||
|
|
||||||
```sh
|
|
||||||
# create a new project in the current directory
|
|
||||||
npx sv create
|
|
||||||
|
|
||||||
# create a new project in my-app
|
|
||||||
npx sv create my-app
|
|
||||||
```
|
|
||||||
|
|
||||||
## Developing
|
|
||||||
|
|
||||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm run dev
|
|
||||||
|
|
||||||
# or start the server and open the app in a new browser tab
|
|
||||||
npm run dev -- --open
|
|
||||||
```
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
To create a production version of your app:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
You can preview the production build with `npm run preview`.
|
|
||||||
|
|
||||||
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
|
||||||
|
|||||||
16
bun.lock
16
bun.lock
@@ -7,7 +7,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@firebase/auth": "^1.12.0",
|
"@firebase/auth": "^1.12.0",
|
||||||
"@resvg/resvg-js": "^2.6.2",
|
"@resvg/resvg-js": "^2.6.2",
|
||||||
"firebase": "^12.8.0",
|
"firebase": "^12.9.0",
|
||||||
"mode-watcher": "^1.1.0",
|
"mode-watcher": "^1.1.0",
|
||||||
"satori": "^0.18.3",
|
"satori": "^0.18.3",
|
||||||
},
|
},
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
|
|
||||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
|
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
|
||||||
|
|
||||||
"@firebase/ai": ["@firebase/ai@2.7.0", "", { "dependencies": { "@firebase/app-check-interop-types": "0.3.3", "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x", "@firebase/app-types": "0.x" } }, "sha512-PwpCz+TtAMWICM7uQNO0mkSPpUKwrMV4NSwHkbVKDvPKoaQmSlO96vIz+Suw2Ao1EaUUsxYb5LGImHWt/fSnRQ=="],
|
"@firebase/ai": ["@firebase/ai@2.8.0", "", { "dependencies": { "@firebase/app-check-interop-types": "0.3.3", "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x", "@firebase/app-types": "0.x" } }, "sha512-grWYGFPsSo+pt+6CYeKR0kWnUfoLLS3xgWPvNrhAS5EPxl6xWq7+HjDZqX24yLneETyl45AVgDsTbVgxeWeRfg=="],
|
||||||
|
|
||||||
"@firebase/analytics": ["@firebase/analytics@0.10.19", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/installations": "0.6.19", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-3wU676fh60gaiVYQEEXsbGS4HbF2XsiBphyvvqDbtC1U4/dO4coshbYktcCHq+HFaGIK07iHOh4pME0hEq1fcg=="],
|
"@firebase/analytics": ["@firebase/analytics@0.10.19", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/installations": "0.6.19", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-3wU676fh60gaiVYQEEXsbGS4HbF2XsiBphyvvqDbtC1U4/dO4coshbYktcCHq+HFaGIK07iHOh4pME0hEq1fcg=="],
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
|
|
||||||
"@firebase/app-check-types": ["@firebase/app-check-types@0.5.3", "", {}, "sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng=="],
|
"@firebase/app-check-types": ["@firebase/app-check-types@0.5.3", "", {}, "sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng=="],
|
||||||
|
|
||||||
"@firebase/app-compat": ["@firebase/app-compat@0.5.7", "", { "dependencies": { "@firebase/app": "0.14.7", "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "tslib": "^2.1.0" } }, "sha512-MO+jfap8IBZQ+K8L2QCiHObyMgpYHrxo4Hc7iJgfb9hjGRW/z1y6LWVdT9wBBK+VJ7cRP2DjAiWQP+thu53hHA=="],
|
"@firebase/app-compat": ["@firebase/app-compat@0.5.8", "", { "dependencies": { "@firebase/app": "0.14.8", "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "tslib": "^2.1.0" } }, "sha512-4De6SUZ36zozl9kh5rZSxKWULpgty27rMzZ6x+xkoo7+NWyhWyFdsdvhFsWhTw/9GGj0wXIcbTjwHYCUIUuHyg=="],
|
||||||
|
|
||||||
"@firebase/app-types": ["@firebase/app-types@0.9.3", "", {}, "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw=="],
|
"@firebase/app-types": ["@firebase/app-types@0.9.3", "", {}, "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw=="],
|
||||||
|
|
||||||
@@ -127,9 +127,9 @@
|
|||||||
|
|
||||||
"@firebase/database-types": ["@firebase/database-types@1.0.16", "", { "dependencies": { "@firebase/app-types": "0.9.3", "@firebase/util": "1.13.0" } }, "sha512-xkQLQfU5De7+SPhEGAXFBnDryUWhhlFXelEg2YeZOQMCdoe7dL64DDAd77SQsR+6uoXIZY5MB4y/inCs4GTfcw=="],
|
"@firebase/database-types": ["@firebase/database-types@1.0.16", "", { "dependencies": { "@firebase/app-types": "0.9.3", "@firebase/util": "1.13.0" } }, "sha512-xkQLQfU5De7+SPhEGAXFBnDryUWhhlFXelEg2YeZOQMCdoe7dL64DDAd77SQsR+6uoXIZY5MB4y/inCs4GTfcw=="],
|
||||||
|
|
||||||
"@firebase/firestore": ["@firebase/firestore@4.10.0", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "@firebase/webchannel-wrapper": "1.0.5", "@grpc/grpc-js": "~1.9.0", "@grpc/proto-loader": "^0.7.8", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-fgF6EbpoagGWh5Vwfu/7/jYgBFwUCwTlPNVF/aSjHcoEDRXpRsIqVfAFTp1LD+dWAUcAKEK3h+osk8spMJXtxA=="],
|
"@firebase/firestore": ["@firebase/firestore@4.11.0", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "@firebase/webchannel-wrapper": "1.0.5", "@grpc/grpc-js": "~1.9.0", "@grpc/proto-loader": "^0.7.8", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-Zb88s8rssBd0J2Tt+NUXMPt2sf+Dq7meatKiJf5t9oto1kZ8w9gK59Koe1uPVbaKfdgBp++N/z0I4G/HamyEhg=="],
|
||||||
|
|
||||||
"@firebase/firestore-compat": ["@firebase/firestore-compat@0.4.4", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/firestore": "4.10.0", "@firebase/firestore-types": "3.0.3", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "sha512-JvxxIgi+D5v9BecjLA1YomdyF7LA6CXhJuVK10b4GtRrB3m2O2hT1jJWbKYZYHUAjTaajkvnos+4U5VNxqkI2w=="],
|
"@firebase/firestore-compat": ["@firebase/firestore-compat@0.4.5", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/firestore": "4.11.0", "@firebase/firestore-types": "3.0.3", "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "sha512-yVX1CkVvqBI4qbA56uZo42xFA4TNU0ICQ+9AFDvYq9U9Xu6iAx9lFDAk/tN+NGereQQXXCSnpISwc/oxsQqPLA=="],
|
||||||
|
|
||||||
"@firebase/firestore-types": ["@firebase/firestore-types@3.0.3", "", { "peerDependencies": { "@firebase/app-types": "0.x", "@firebase/util": "1.x" } }, "sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q=="],
|
"@firebase/firestore-types": ["@firebase/firestore-types@3.0.3", "", { "peerDependencies": { "@firebase/app-types": "0.x", "@firebase/util": "1.x" } }, "sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q=="],
|
||||||
|
|
||||||
@@ -451,7 +451,7 @@
|
|||||||
|
|
||||||
"file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
|
"file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
|
||||||
|
|
||||||
"firebase": ["firebase@12.8.0", "", { "dependencies": { "@firebase/ai": "2.7.0", "@firebase/analytics": "0.10.19", "@firebase/analytics-compat": "0.2.25", "@firebase/app": "0.14.7", "@firebase/app-check": "0.11.0", "@firebase/app-check-compat": "0.4.0", "@firebase/app-compat": "0.5.7", "@firebase/app-types": "0.9.3", "@firebase/auth": "1.12.0", "@firebase/auth-compat": "0.6.2", "@firebase/data-connect": "0.3.12", "@firebase/database": "1.1.0", "@firebase/database-compat": "2.1.0", "@firebase/firestore": "4.10.0", "@firebase/firestore-compat": "0.4.4", "@firebase/functions": "0.13.1", "@firebase/functions-compat": "0.4.1", "@firebase/installations": "0.6.19", "@firebase/installations-compat": "0.2.19", "@firebase/messaging": "0.12.23", "@firebase/messaging-compat": "0.2.23", "@firebase/performance": "0.7.9", "@firebase/performance-compat": "0.2.22", "@firebase/remote-config": "0.8.0", "@firebase/remote-config-compat": "0.2.21", "@firebase/storage": "0.14.0", "@firebase/storage-compat": "0.4.0", "@firebase/util": "1.13.0" } }, "sha512-S1tCIR3ENecee0tY2cfTHfMkXqkitHfbsvqpCtvsT0Zi9vDB7A4CodAjHfHCjVvu/XtGy1LHLjOasVcF10rCVw=="],
|
"firebase": ["firebase@12.9.0", "", { "dependencies": { "@firebase/ai": "2.8.0", "@firebase/analytics": "0.10.19", "@firebase/analytics-compat": "0.2.25", "@firebase/app": "0.14.8", "@firebase/app-check": "0.11.0", "@firebase/app-check-compat": "0.4.0", "@firebase/app-compat": "0.5.8", "@firebase/app-types": "0.9.3", "@firebase/auth": "1.12.0", "@firebase/auth-compat": "0.6.2", "@firebase/data-connect": "0.3.12", "@firebase/database": "1.1.0", "@firebase/database-compat": "2.1.0", "@firebase/firestore": "4.11.0", "@firebase/firestore-compat": "0.4.5", "@firebase/functions": "0.13.1", "@firebase/functions-compat": "0.4.1", "@firebase/installations": "0.6.19", "@firebase/installations-compat": "0.2.19", "@firebase/messaging": "0.12.23", "@firebase/messaging-compat": "0.2.23", "@firebase/performance": "0.7.9", "@firebase/performance-compat": "0.2.22", "@firebase/remote-config": "0.8.0", "@firebase/remote-config-compat": "0.2.21", "@firebase/storage": "0.14.0", "@firebase/storage-compat": "0.4.0", "@firebase/util": "1.13.0" } }, "sha512-CwwTYoqZg6KxygPOaaJqIc4aoLvo0RCRrXoln9GoxLE8QyAwTydBaSLGVlR4WPcuOgN3OEL0tJLT1H4IU/dv7w=="],
|
||||||
|
|
||||||
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
|
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
|
||||||
|
|
||||||
@@ -681,6 +681,8 @@
|
|||||||
|
|
||||||
"zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="],
|
"zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="],
|
||||||
|
|
||||||
|
"@firebase/app-compat/@firebase/app": ["@firebase/app@0.14.8", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "idb": "7.1.1", "tslib": "^2.1.0" } }, "sha512-WiE9uCGRLUnShdjb9iP20sA3ToWrBbNXr14/N5mow7Nls9dmKgfGaGX5cynLvrltxq2OrDLh1VDNaUgsnS/k/g=="],
|
||||||
|
|
||||||
"@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
"@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw=="],
|
||||||
@@ -699,6 +701,8 @@
|
|||||||
|
|
||||||
"cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
"cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||||
|
|
||||||
|
"firebase/@firebase/app": ["@firebase/app@0.14.8", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "idb": "7.1.1", "tslib": "^2.1.0" } }, "sha512-WiE9uCGRLUnShdjb9iP20sA3ToWrBbNXr14/N5mow7Nls9dmKgfGaGX5cynLvrltxq2OrDLh1VDNaUgsnS/k/g=="],
|
||||||
|
|
||||||
"mode-watcher/runed": ["runed@0.25.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-7+ma4AG9FT2sWQEA0Egf6mb7PBT2vHyuHail1ie8ropfSjvZGtEAx8YTmUjv/APCsdRRxEVvArNjALk9zFSOrg=="],
|
"mode-watcher/runed": ["runed@0.25.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-7+ma4AG9FT2sWQEA0Egf6mb7PBT2vHyuHail1ie8ropfSjvZGtEAx8YTmUjv/APCsdRRxEVvArNjALk9zFSOrg=="],
|
||||||
|
|
||||||
"mode-watcher/svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="],
|
"mode-watcher/svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="],
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@firebase/auth": "^1.12.0",
|
"@firebase/auth": "^1.12.0",
|
||||||
"@resvg/resvg-js": "^2.6.2",
|
"@resvg/resvg-js": "^2.6.2",
|
||||||
"firebase": "^12.8.0",
|
"firebase": "^12.9.0",
|
||||||
"mode-watcher": "^1.1.0",
|
"mode-watcher": "^1.1.0",
|
||||||
"satori": "^0.18.3"
|
"satori": "^0.18.3"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,21 +11,23 @@
|
|||||||
import { seguirUsuario } from '@/hooks/seguirUsuario';
|
import { seguirUsuario } from '@/hooks/seguirUsuario';
|
||||||
import type { Post } from '../../types';
|
import type { Post } from '../../types';
|
||||||
import CardError from './CardError.svelte';
|
import CardError from './CardError.svelte';
|
||||||
import { cacheSeguidos } from '@/stores/cacheSeguidos.svelte';
|
import { cacheSeguidos } from '@/stores/cacheSeguidos.js';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
post,
|
post,
|
||||||
variant = 'icon-lg'
|
variant = 'icon-lg'
|
||||||
}: { post: Omit<Partial<Post>, 'authorId'> & { authorId: string }; variant?: string } = $props();
|
}: {
|
||||||
|
post: Omit<Partial<Post>, 'authorId'> & { authorId: string; id: string };
|
||||||
|
variant?: 'icon-lg' | 'default' | 'sm' | 'lg' | 'icon' | 'icon-sm';
|
||||||
|
} = $props();
|
||||||
|
|
||||||
let seguido: Boolean | null = $state(null);
|
let seguido: boolean | null = $state(null);
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
window.addEventListener('followCacheUpdated', ((
|
window.addEventListener('followCacheUpdated', ((
|
||||||
event: CustomEvent<{ userId: string; isFollowed: boolean } | { clearAll: true }>
|
event: CustomEvent<{ userId: string; isFollowed: boolean } | { clearAll: true }>
|
||||||
) => {
|
) => {
|
||||||
if ('clearAll' in event.detail && event.detail.clearAll === true) {
|
if ('clearAll' in event.detail && event.detail.clearAll === true) {
|
||||||
cargarSeguido();
|
|
||||||
} else if ('userId' in event.detail && event.detail.userId === post.authorId) {
|
} else if ('userId' in event.detail && event.detail.userId === post.authorId) {
|
||||||
seguido = event.detail.isFollowed;
|
seguido = event.detail.isFollowed;
|
||||||
}
|
}
|
||||||
@@ -39,14 +41,13 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function cargarSeguido() {
|
async function cargarSeguido() {
|
||||||
let a = cacheSeguidos.get(post.authorId);
|
seguido = await cacheSeguidos.getOrFetch(
|
||||||
if (a === undefined) {
|
post.authorId,
|
||||||
const seguidoStatus = await esSeguido(post);
|
async () => {
|
||||||
cacheSeguidos.set(post.authorId, seguidoStatus.isFollowing || false);
|
const seguidoStatus = await esSeguido(post as Post);
|
||||||
seguido = seguidoStatus.isFollowing || false;
|
return seguidoStatus?.isFollowing || false;
|
||||||
return;
|
}
|
||||||
}
|
);
|
||||||
seguido = a;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mensajeError: string | null = $state(null);
|
let mensajeError: string | null = $state(null);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
let usu = $state({ displayName: data.displayName, bio: data.bio });
|
let usu = $state({ displayName: data.displayName, bio: data.bio });
|
||||||
|
|
||||||
let contenido = $derived(() => {
|
let contenido = $derived(() => {
|
||||||
|
if (data.bio == '') return '';
|
||||||
let t = data.bio
|
let t = data.bio
|
||||||
.replaceAll('&', '')
|
.replaceAll('&', '')
|
||||||
.replaceAll('<', '')
|
.replaceAll('<', '')
|
||||||
@@ -104,10 +105,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 class="mt-10 scroll-m-20 text-center text-2xl font-extrabold tracking-tight lg:text-5xl">
|
<h1 class="mt-10 scroll-m-20 text-center text-2xl font-extrabold tracking-tight lg:text-5xl">
|
||||||
{usu.displayName}
|
{data.displayName}
|
||||||
<p class="ml-2 text-2xl font-medium text-muted-foreground">@{data.username}</p>
|
<p class="ml-2 text-2xl font-medium text-muted-foreground">@{data.username}</p>
|
||||||
</h1>
|
</h1>
|
||||||
{#if usu.bio}
|
{#if data.bio}
|
||||||
<p class="mt-4 rounded-4xl bg-accent p-4 text-center text-muted-foreground">
|
<p class="mt-4 rounded-4xl bg-accent p-4 text-center text-muted-foreground">
|
||||||
{@html contenido()}
|
{@html contenido()}
|
||||||
<!-- {usu.bio.replaceAll('<', '')} -->
|
<!-- {usu.bio.replaceAll('<', '')} -->
|
||||||
@@ -129,12 +130,12 @@
|
|||||||
</Avatar>
|
</Avatar>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="mt-10 scroll-m-20 text-center text-2xl font-extrabold tracking-tight lg:text-5xl">
|
<h1 class="mt-10 scroll-m-20 text-center text-2xl font-extrabold tracking-tight lg:text-5xl">
|
||||||
{usu.displayName}
|
{data.displayName}
|
||||||
<p class="ml-2 text-2xl font-medium text-muted-foreground">@{data.username}</p>
|
<p class="ml-2 text-2xl font-medium text-muted-foreground">@{data.username}</p>
|
||||||
</h1>
|
</h1>
|
||||||
{#if usu.bio}
|
{#if data.bio}
|
||||||
<p class="mt-4 rounded-4xl bg-accent p-4 text-center text-muted-foreground">
|
<p class="mt-4 rounded-4xl bg-accent p-4 text-center text-muted-foreground">
|
||||||
{@html usu.bio.replaceAll('\n', '<br>')}
|
{@html contenido()}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
import { updateUsuario } from '@/hooks/updateUsuario';
|
import { updateUsuario } from '@/hooks/updateUsuario';
|
||||||
import DialogFooter from './ui/dialog/dialog-footer.svelte';
|
import DialogFooter from './ui/dialog/dialog-footer.svelte';
|
||||||
import Spinner from './ui/spinner/spinner.svelte';
|
import Spinner from './ui/spinner/spinner.svelte';
|
||||||
import { invalidate } from '$app/navigation';
|
import { invalidate, invalidateAll } from '$app/navigation';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
|
|
||||||
let { data = $bindable(), children } = $props();
|
let { data = $bindable(), children } = $props();
|
||||||
@@ -42,9 +42,9 @@
|
|||||||
});
|
});
|
||||||
cargando = false;
|
cargando = false;
|
||||||
open = false;
|
open = false;
|
||||||
// invalidateAll();
|
await invalidateAll();
|
||||||
await invalidate(page.url);
|
// await invalidate(page.url);
|
||||||
await invalidate('perfil:general');
|
// await invalidate('perfil:general');
|
||||||
}
|
}
|
||||||
|
|
||||||
function onkeydown(e: KeyboardEvent) {
|
function onkeydown(e: KeyboardEvent) {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
}
|
}
|
||||||
cargando = true;
|
cargando = true;
|
||||||
try {
|
try {
|
||||||
await cambiarContraseñaUsuario(data.id, passwordData.oldPassword, passwordData.newPassword);
|
await cambiarContraseñaUsuario(passwordData.oldPassword, passwordData.newPassword, data.id);
|
||||||
cargando = false;
|
cargando = false;
|
||||||
open = false;
|
open = false;
|
||||||
passwordData.oldPassword = '';
|
passwordData.oldPassword = '';
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
</FieldGroup>
|
</FieldGroup>
|
||||||
<Button type="submit" disabled={!coinsiden || cargando}>
|
<Button type="submit" class="mt-6" disabled={!coinsiden || cargando}>
|
||||||
{#if cargando}
|
{#if cargando}
|
||||||
<Spinner />
|
<Spinner />
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
16
src/lib/components/MostrarVolver.svelte.ts
Normal file
16
src/lib/components/MostrarVolver.svelte.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
export class MostrarVolver {
|
||||||
|
scrollY = $state(0);
|
||||||
|
value = $derived(this.scrollY > 500);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
$effect(() => {
|
||||||
|
const handleScroll = () => {
|
||||||
|
this.scrollY = window.scrollY;
|
||||||
|
};
|
||||||
|
window.addEventListener('scroll', handleScroll);
|
||||||
|
return () => window.removeEventListener('scroll', handleScroll);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,12 +16,7 @@
|
|||||||
import TooltipTrigger from './ui/tooltip/tooltip-trigger.svelte';
|
import TooltipTrigger from './ui/tooltip/tooltip-trigger.svelte';
|
||||||
import TooltipContent from './ui/tooltip/tooltip-content.svelte';
|
import TooltipContent from './ui/tooltip/tooltip-content.svelte';
|
||||||
import RecuperarContraseña from './admin/RecuperarContraseña.svelte';
|
import RecuperarContraseña from './admin/RecuperarContraseña.svelte';
|
||||||
import { Dialog } from './ui/dialog';
|
|
||||||
import DialogContent from './ui/dialog/dialog-content.svelte';
|
|
||||||
import ModificarUsuario from './admin/ModificarUsuario.svelte';
|
import ModificarUsuario from './admin/ModificarUsuario.svelte';
|
||||||
import { fade } from 'svelte/transition';
|
|
||||||
import type { Unsubscriber } from 'svelte/store';
|
|
||||||
import Input from './ui/input/input.svelte';
|
|
||||||
import Trash_2 from '@lucide/svelte/icons/trash-2';
|
import Trash_2 from '@lucide/svelte/icons/trash-2';
|
||||||
import BorrarUsuario from './BorrarUsuario.svelte';
|
import BorrarUsuario from './BorrarUsuario.svelte';
|
||||||
import InputGroup from './ui/input-group/input-group.svelte';
|
import InputGroup from './ui/input-group/input-group.svelte';
|
||||||
@@ -29,12 +24,26 @@
|
|||||||
import InputGroupInput from './ui/input-group/input-group-input.svelte';
|
import InputGroupInput from './ui/input-group/input-group-input.svelte';
|
||||||
import AgregarUsuario from './admin/AgregarUsuario.svelte';
|
import AgregarUsuario from './admin/AgregarUsuario.svelte';
|
||||||
import DarAdmin from './admin/DarAdmin.svelte';
|
import DarAdmin from './admin/DarAdmin.svelte';
|
||||||
|
import { busquedaAdminUsuarios } from '@/hooks/busquedaAdminUsuarios';
|
||||||
|
import { invalidate, replaceState } from '$app/navigation';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
usuarios: UserResponseDto[];
|
usuarios: UserResponseDto[];
|
||||||
|
hayMas: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { usuarios = $bindable() }: Props = $props();
|
let { usuarios = $bindable(), hayMas }: Props = $props();
|
||||||
|
|
||||||
|
let paginaActual = $derived.by(() => {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
return Number(url.searchParams.get('p')) || 1;
|
||||||
|
});
|
||||||
|
let search = $derived.by(() => {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
let ret = url.searchParams.get('q') || '';
|
||||||
|
return ret;
|
||||||
|
});
|
||||||
|
let hayMass = $derived(hayMas);
|
||||||
|
|
||||||
let open = $state(false);
|
let open = $state(false);
|
||||||
let openModificarUsuario = $state(false);
|
let openModificarUsuario = $state(false);
|
||||||
@@ -43,20 +52,16 @@
|
|||||||
let opencrearUsuario = $state(false);
|
let opencrearUsuario = $state(false);
|
||||||
|
|
||||||
let usuarioBorrar: UserResponseDto | null = $state(null);
|
let usuarioBorrar: UserResponseDto | null = $state(null);
|
||||||
|
|
||||||
//si ponia contraseña en español quedaba muy largo el nombre
|
|
||||||
let usuarioCambioPass: UserResponseDto | null = $state(null);
|
let usuarioCambioPass: UserResponseDto | null = $state(null);
|
||||||
|
|
||||||
let usuarioModificar: UserResponseDto | null = $state(null);
|
let usuarioModificar: UserResponseDto | null = $state(null);
|
||||||
|
|
||||||
let usuarioDarAdmin: UserResponseDto | null = $state(null);
|
let usuarioDarAdmin: UserResponseDto | null = $state(null);
|
||||||
|
|
||||||
let search = $state('');
|
|
||||||
|
|
||||||
type SortKey = 'username' | 'displayName' | 'postsCount' | 'createdAt';
|
type SortKey = 'username' | 'displayName' | 'postsCount' | 'createdAt';
|
||||||
let sortBy = $state<SortKey | null>(null);
|
let sortBy = $state<SortKey | null>(null);
|
||||||
let sortDirection = $state<'asc' | 'desc'>('asc');
|
let sortDirection = $state<'asc' | 'desc'>('asc');
|
||||||
|
|
||||||
|
let usuariosFiltrados = $derived(usuarios);
|
||||||
|
|
||||||
function ordenarPor(campo: SortKey) {
|
function ordenarPor(campo: SortKey) {
|
||||||
if (sortBy === campo) {
|
if (sortBy === campo) {
|
||||||
sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
|
sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
|
||||||
@@ -64,70 +69,75 @@
|
|||||||
sortBy = campo;
|
sortBy = campo;
|
||||||
sortDirection = 'asc';
|
sortDirection = 'asc';
|
||||||
}
|
}
|
||||||
|
usuariosFiltrados = usuariosFiltrados.toSorted((a, b) => {
|
||||||
|
if (!sortBy) return 0;
|
||||||
|
|
||||||
|
const key: SortKey = sortBy;
|
||||||
|
|
||||||
|
if (key === 'createdAt') {
|
||||||
|
const ta = new Date(a.createdAt).getTime();
|
||||||
|
const tb = new Date(b.createdAt).getTime();
|
||||||
|
return sortDirection === 'asc' ? ta - tb : tb - ta;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'postsCount') {
|
||||||
|
return sortDirection === 'asc' ? a.postsCount - b.postsCount : b.postsCount - a.postsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sa = a[key].toString().toLowerCase();
|
||||||
|
const sb = b[key].toString().toLowerCase();
|
||||||
|
return sortDirection === 'asc' ? sa.localeCompare(sb) : sb.localeCompare(sa);
|
||||||
|
});
|
||||||
|
paginaActual = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let usuariosFiltrados = $derived(
|
|
||||||
usuarios
|
|
||||||
.filter(
|
|
||||||
(u) =>
|
|
||||||
u.username.toLowerCase().startsWith(search.toLowerCase()) ||
|
|
||||||
u.displayName.toLowerCase().startsWith(search.toLowerCase())
|
|
||||||
)
|
|
||||||
.toSorted((a, b) => {
|
|
||||||
if (!sortBy) return 0;
|
|
||||||
|
|
||||||
const key: SortKey = sortBy;
|
|
||||||
|
|
||||||
if (key === 'createdAt') {
|
|
||||||
const ta = new Date(a.createdAt).getTime();
|
|
||||||
const tb = new Date(b.createdAt).getTime();
|
|
||||||
return sortDirection === 'asc' ? ta - tb : tb - ta;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === 'postsCount') {
|
|
||||||
return sortDirection === 'asc'
|
|
||||||
? a.postsCount - b.postsCount
|
|
||||||
: b.postsCount - a.postsCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sa = a[key].toString().toLowerCase();
|
|
||||||
const sb = b[key].toString().toLowerCase();
|
|
||||||
return sortDirection === 'asc' ? sa.localeCompare(sb) : sb.localeCompare(sa);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
function getSortIcon(campo: SortKey) {
|
function getSortIcon(campo: SortKey) {
|
||||||
if (sortBy !== campo) return '';
|
if (sortBy !== campo) return '';
|
||||||
return sortDirection === 'asc' ? '↑' : '↓';
|
return sortDirection === 'asc' ? '↑' : '↓';
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCambiarContraseña(usuario: UserResponseDto) {
|
|
||||||
open = true;
|
|
||||||
usuarioCambioPass = usuario;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleModificar(usuario: UserResponseDto) {
|
|
||||||
openModificarUsuario = true;
|
|
||||||
usuarioModificar = usuario;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleBorrar(usuario: UserResponseDto) {
|
|
||||||
openBorrar = true;
|
|
||||||
usuarioBorrar = usuario;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDarAdmin(usuario: UserResponseDto) {
|
|
||||||
openDarAdmin = true;
|
|
||||||
usuarioDarAdmin = usuario;
|
|
||||||
}
|
|
||||||
|
|
||||||
// $inspect(usuarios);
|
// $inspect(usuarios);
|
||||||
|
let timeoutId: ReturnType<typeof setTimeout> | number | undefined;
|
||||||
|
function buscarUsuarios() {
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutId = setTimeout(async () => {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
if (!search.trim()) {
|
||||||
|
url.searchParams.delete('q');
|
||||||
|
} else {
|
||||||
|
url.searchParams.set('q', search);
|
||||||
|
}
|
||||||
|
replaceState(url, {});
|
||||||
|
|
||||||
|
let ret = await busquedaAdminUsuarios(search, ITEMS_POR_PAGINA, paginaActual);
|
||||||
|
usuariosFiltrados = ret.usuarios;
|
||||||
|
// invalidate('admin:load');
|
||||||
|
hayMass = ret.hayMas;
|
||||||
|
}, 200);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (timeoutId) clearTimeout(timeoutId);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const ITEMS_POR_PAGINA = 5;
|
||||||
|
|
||||||
|
// const usuariosPaginados = $derived(
|
||||||
|
// usuariosFiltrados.slice((paginaActual - 1) * ITEMS_POR_PAGINA, paginaActual * ITEMS_POR_PAGINA)
|
||||||
|
// );
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mb-4 flex gap-2">
|
<div class="mb-4 flex gap-2">
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<InputGroupAddon align="inline-start"><Search></Search></InputGroupAddon>
|
<InputGroupAddon align="inline-start"><Search></Search></InputGroupAddon>
|
||||||
<InputGroupInput type="text" placeholder="Buscar usuario..." bind:value={search} />
|
<InputGroupInput
|
||||||
|
type="text"
|
||||||
|
placeholder="Buscar usuario..."
|
||||||
|
bind:value={search}
|
||||||
|
oninput={() => buscarUsuarios()}
|
||||||
|
/>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
<Button
|
<Button
|
||||||
onclick={() => (opencrearUsuario = !opencrearUsuario)}
|
onclick={() => (opencrearUsuario = !opencrearUsuario)}
|
||||||
@@ -174,7 +184,11 @@
|
|||||||
<TableCell class="flex gap-2">
|
<TableCell class="flex gap-2">
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<Button onclick={() => handleCambiarContraseña(usuario)}><KeyIcon></KeyIcon></Button
|
<Button
|
||||||
|
onclick={() => {
|
||||||
|
open = true;
|
||||||
|
usuarioCambioPass = usuario;
|
||||||
|
}}><KeyIcon></KeyIcon></Button
|
||||||
>
|
>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
@@ -183,7 +197,12 @@
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<Button onclick={() => handleModificar(usuario)}><UserPen /></Button>
|
<Button
|
||||||
|
onclick={() => {
|
||||||
|
openModificarUsuario = true;
|
||||||
|
usuarioModificar = usuario;
|
||||||
|
}}><UserPen /></Button
|
||||||
|
>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>Modificar Usuario</p>
|
<p>Modificar Usuario</p>
|
||||||
@@ -193,7 +212,10 @@
|
|||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<Button
|
<Button
|
||||||
disabled={usuario.isAdmin}
|
disabled={usuario.isAdmin}
|
||||||
onclick={() => handleBorrar(usuario)}
|
onclick={() => {
|
||||||
|
openBorrar = true;
|
||||||
|
usuarioBorrar = usuario;
|
||||||
|
}}
|
||||||
variant="destructive"><Trash_2 /></Button
|
variant="destructive"><Trash_2 /></Button
|
||||||
>
|
>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
@@ -209,7 +231,10 @@
|
|||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<Button
|
<Button
|
||||||
onclick={() => handleDarAdmin(usuario)}
|
onclick={() => {
|
||||||
|
openDarAdmin = true;
|
||||||
|
usuarioDarAdmin = usuario;
|
||||||
|
}}
|
||||||
variant={usuario.isAdmin ? 'destructive' : 'default'}
|
variant={usuario.isAdmin ? 'destructive' : 'default'}
|
||||||
>
|
>
|
||||||
<Shield />
|
<Shield />
|
||||||
@@ -229,6 +254,31 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
<div class="mt-4 flex items-center justify-between">
|
||||||
|
<Button
|
||||||
|
disabled={paginaActual === 1}
|
||||||
|
onclick={() => {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
url.searchParams.set('p', String(--paginaActual));
|
||||||
|
replaceState(url, {});
|
||||||
|
buscarUsuarios();
|
||||||
|
}}
|
||||||
|
variant="secondary"
|
||||||
|
>
|
||||||
|
Anterior
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
disabled={!hayMass}
|
||||||
|
onclick={() => {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
url.searchParams.set('p', String(++paginaActual));
|
||||||
|
replaceState(url, {});
|
||||||
|
buscarUsuarios();
|
||||||
|
}}
|
||||||
|
variant="secondary">Siguiente</Button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
<BorrarUsuario bind:open={openBorrar} usuario={usuarioBorrar} />
|
<BorrarUsuario bind:open={openBorrar} usuario={usuarioBorrar} />
|
||||||
<RecuperarContraseña bind:open usuario={usuarioCambioPass} />
|
<RecuperarContraseña bind:open usuario={usuarioCambioPass} />
|
||||||
<ModificarUsuario bind:open={openModificarUsuario} bind:usuario={usuarioModificar} />
|
<ModificarUsuario bind:open={openModificarUsuario} bind:usuario={usuarioModificar} />
|
||||||
|
|||||||
@@ -35,8 +35,5 @@
|
|||||||
<BotonSeguir post={{ authorId: usu.id }} />
|
<BotonSeguir post={{ authorId: usu.id }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if usu.bio}
|
|
||||||
<div class="mt-4 rounded-full bg-accent p-4 text-muted-foreground">{usu.bio}</div>
|
|
||||||
{/if}
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
27
src/lib/components/VolverArriba.svelte
Normal file
27
src/lib/components/VolverArriba.svelte
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<script>
|
||||||
|
import { fade, scale } from 'svelte/transition';
|
||||||
|
import Button from './ui/button/button.svelte';
|
||||||
|
import { MostrarVolver } from './MostrarVolver.svelte';
|
||||||
|
import ArrowUp from '@lucide/svelte/icons/arrow-up';
|
||||||
|
|
||||||
|
let mostrarVolver = new MostrarVolver();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if mostrarVolver.value}
|
||||||
|
<div transition:fade>
|
||||||
|
<div class="fixed right-9 bottom-12 flex flex-col gap-2" transition:scale>
|
||||||
|
<Button
|
||||||
|
onclick={() => {
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
class="align-middle"
|
||||||
|
>
|
||||||
|
Volver
|
||||||
|
<ArrowUp />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
@@ -30,13 +30,7 @@
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<div
|
{mensajeResultado}
|
||||||
class={esExitoso
|
|
||||||
? 'rounded border border-green-400 bg-green-100/10 px-4 py-3 text-green-700'
|
|
||||||
: 'rounded border border-red-400 bg-red-100/10 px-4 py-3 text-red-700'}
|
|
||||||
>
|
|
||||||
{mensajeResultado}
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
import Label from '../ui/label/label.svelte';
|
import Label from '../ui/label/label.svelte';
|
||||||
import Spinner from '../ui/spinner/spinner.svelte';
|
import Spinner from '../ui/spinner/spinner.svelte';
|
||||||
import { updateUsuario } from '@/hooks/updateUsuario';
|
import { updateUsuario } from '@/hooks/updateUsuario';
|
||||||
|
import { invalidate } from '$app/navigation';
|
||||||
|
|
||||||
interface Prop {
|
interface Prop {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -38,6 +39,7 @@
|
|||||||
error = ret;
|
error = ret;
|
||||||
} else {
|
} else {
|
||||||
usuario!.displayName = ret.displayName;
|
usuario!.displayName = ret.displayName;
|
||||||
|
invalidate('admin:load');
|
||||||
open = false;
|
open = false;
|
||||||
}
|
}
|
||||||
cargando = false;
|
cargando = false;
|
||||||
|
|||||||
@@ -50,13 +50,13 @@
|
|||||||
<header class="border-b bg-background/95 backdrop-blur">
|
<header class="border-b bg-background/95 backdrop-blur">
|
||||||
<div class="mx-4 ms-2 flex h-12 items-center justify-between">
|
<div class="mx-4 ms-2 flex h-12 items-center justify-between">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<a href="/" class="mr-6 flex items-center space-x-2">
|
<button class="mr-6 flex items-center space-x-2" onclick={() => (window.location.href = '/')}>
|
||||||
<Avatar
|
<Avatar
|
||||||
class="h-8 w-8 transform rounded-sm! transition-transform duration-300 ease-in-out hover:scale-130 hover:rotate-12"
|
class="h-8 w-8 transform rounded-sm! transition-transform duration-300 ease-in-out hover:scale-130 hover:rotate-12"
|
||||||
>
|
>
|
||||||
<AvatarImage src="/x.png" alt="minix" />
|
<AvatarImage src="/x.png" alt="minix" />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
</a>
|
</button>
|
||||||
<!-- <nav class="me-2 items-center space-x-6 text-sm font-medium md:flex">
|
<!-- <nav class="me-2 items-center space-x-6 text-sm font-medium md:flex">
|
||||||
<ButtonTheme />
|
<ButtonTheme />
|
||||||
</nav> -->
|
</nav> -->
|
||||||
|
|||||||
25
src/lib/hooks/UsuariosAdmin.ts
Normal file
25
src/lib/hooks/UsuariosAdmin.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { apiBase } from '@/stores/url';
|
||||||
|
import { sesionStore } from '@/stores/usuario';
|
||||||
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
import { get } from 'svelte/store';
|
||||||
|
import type { UserResponseDto } from '../../types';
|
||||||
|
|
||||||
|
export async function fetchUsuariosAdmin(page: number, limit: number) {
|
||||||
|
let response = await fetch(get(apiBase) + `/api/admin/users?page=${page}&pageSize=${limit}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status === 401) {
|
||||||
|
throw redirect(302, '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return { error: true };
|
||||||
|
}
|
||||||
|
const ret: { usuarios: UserResponseDto[]; hayMas: boolean } = await response.json();
|
||||||
|
return { ret, error: false };
|
||||||
|
}
|
||||||
26
src/lib/hooks/busquedaAdminUsuarios.ts
Normal file
26
src/lib/hooks/busquedaAdminUsuarios.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { apiBase } from '@/stores/url';
|
||||||
|
import { sesionStore } from '@/stores/usuario';
|
||||||
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
|
export async function busquedaAdminUsuarios(q: string, limit = 5, page = 1, fetch2?: Function) {
|
||||||
|
try {
|
||||||
|
const fetchFn = fetch2 ? fetch2 : fetch;
|
||||||
|
const response = await fetchFn(
|
||||||
|
get(apiBase) +
|
||||||
|
`/api/admin/users${q ? `?q=${q}` : ''}${q ? '&' : '?'}page=${page}&pageSize=${limit}`,
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (response.ok) {
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
import { apiBase } from "@/stores/url";
|
import { apiBase } from '@/stores/url';
|
||||||
import { get } from "svelte/store";
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
export async function checkEmail(email: string) {
|
export async function checkEmail(email: string) {
|
||||||
try {
|
try {
|
||||||
const req = await fetch(`${get(apiBase)}/api/users/check-email/${email}`, {
|
const req = await fetch(`${get(apiBase)}/api/users/check-email/${email}`, {
|
||||||
method: "GET"
|
method: 'GET'
|
||||||
});
|
});
|
||||||
if (req.ok){
|
if (req.ok) {
|
||||||
return (await req.json()).available;
|
return (await req.json()).available;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { apiBase } from "@/stores/url";
|
import { apiBase } from '@/stores/url';
|
||||||
import { get } from "svelte/store";
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
export async function checkUsername(username: string) {
|
export async function checkUsername(username: string) {
|
||||||
try {
|
try {
|
||||||
const req = await fetch(`${get(apiBase)}/api/users/check-username/${username}`, {
|
const req = await fetch(`${get(apiBase)}/api/users/check-username/${username}`, {
|
||||||
method: "GET"
|
method: 'GET'
|
||||||
});
|
});
|
||||||
if (req.ok){
|
if (req.ok) {
|
||||||
return (await req.json()).available;
|
return (await req.json()).available;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { apiBase } from "@/stores/url";
|
import { apiBase } from '@/stores/url';
|
||||||
import { sesionStore } from "@/stores/usuario";
|
import { sesionStore } from '@/stores/usuario';
|
||||||
import { get } from "svelte/store";
|
import { get } from 'svelte/store';
|
||||||
import type { Post } from '../../types';
|
import type { Post } from '../../types';
|
||||||
import { PAGE_SIZE } from '../stores/posts';
|
import { PAGE_SIZE } from '../stores/posts';
|
||||||
|
|
||||||
@@ -10,10 +10,9 @@ export async function getPosts(page: number = 1): Promise<Post[]> {
|
|||||||
const headers: HeadersInit = {};
|
const headers: HeadersInit = {};
|
||||||
if (token) headers.Authorization = `Bearer ${token}`;
|
if (token) headers.Authorization = `Bearer ${token}`;
|
||||||
|
|
||||||
const res = await fetch(
|
const res = await fetch(`${get(apiBase)}/timeline?page=${page}&pageSize=${PAGE_SIZE}`, {
|
||||||
`${get(apiBase)}/timeline?page=${page}&pageSize=${PAGE_SIZE}`,
|
headers
|
||||||
{ headers }
|
});
|
||||||
);
|
|
||||||
|
|
||||||
if (!res.ok) throw new Error('Error cargando posts');
|
if (!res.ok) throw new Error('Error cargando posts');
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { sesionStore } from '@/stores/usuario';
|
|||||||
import type { Post } from '../../types';
|
import type { Post } from '../../types';
|
||||||
|
|
||||||
export async function likePost(post: Post) {
|
export async function likePost(post: Post) {
|
||||||
let method = post.isLiked ? "DELETE" : "POST";
|
let method = post.isLiked ? 'DELETE' : 'POST';
|
||||||
try {
|
try {
|
||||||
const req = await fetch(get(apiBase) + `/api/posts/${post.id}/like`, {
|
const req = await fetch(get(apiBase) + `/api/posts/${post.id}/like`, {
|
||||||
method: method,
|
method: method,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import { page, loadingPosts, PAGE_SIZE } from '@/stores/posts';
|
import { page, loadingPosts, PAGE_SIZE, resetPosts } from '@/stores/posts';
|
||||||
import { appendPosts } from '@/stores/posts';
|
import { appendPosts } from '@/stores/posts';
|
||||||
import { getPosts } from './getPosts';
|
import { getPosts } from './getPosts';
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ export async function loadMorePosts() {
|
|||||||
if (newPosts.length < PAGE_SIZE) {
|
if (newPosts.length < PAGE_SIZE) {
|
||||||
finished = true;
|
finished = true;
|
||||||
} else {
|
} else {
|
||||||
page.update(p => p + 1);
|
page.update((p) => p + 1);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loadingPosts.set(false);
|
loadingPosts.set(false);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { cacheSeguidos } from '@/stores/cacheSeguidos.svelte';
|
import { cacheSeguidos } from '@/stores/cacheSeguidos';
|
||||||
import { apiBase } from '@/stores/url';
|
import { apiBase } from '@/stores/url';
|
||||||
import { sesionStore } from '@/stores/usuario';
|
import { sesionStore } from '@/stores/usuario';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
|
|||||||
@@ -2,14 +2,18 @@ import { apiBase } from '@/stores/url';
|
|||||||
import { sesionStore } from '@/stores/usuario';
|
import { sesionStore } from '@/stores/usuario';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
export async function obtenerCantidadDeUsosdeHtag(htag: string, fetch2?: Function) {
|
export async function obtenerCantidadDeUsosdeHtag(
|
||||||
|
htag: string,
|
||||||
|
fetch2?: Function,
|
||||||
|
page: number = 1,
|
||||||
|
limit: number = 20,) {
|
||||||
if (!htag) return null;
|
if (!htag) return null;
|
||||||
const fetchFn = fetch2 || fetch;
|
const fetchFn = fetch2 || fetch;
|
||||||
try {
|
try {
|
||||||
const req = await fetchFn(`${get(apiBase)}/api/posts/hashtag/${htag}`, {
|
const req = await fetchFn(`${get(apiBase)}/api/posts/hashtag/${htag}?page=${page}&pageSize=${limit}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
Authorization: `Bearer ${get(sesionStore)?.accessToken || ''}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (req.ok) {
|
if (req.ok) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export async function obtenerPostPorId(
|
|||||||
const req = await fetchFn(`${get(apiBase)}/api/posts/${idpost}`, {
|
const req = await fetchFn(`${get(apiBase)}/api/posts/${idpost}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
Authorization: `Bearer ${get(sesionStore)?.accessToken || ''}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let data = await req.json();
|
let data = await req.json();
|
||||||
|
|||||||
@@ -6,15 +6,16 @@ import type { Post } from '../../types';
|
|||||||
export async function obtenerRespuestasPorId(
|
export async function obtenerRespuestasPorId(
|
||||||
id: string,
|
id: string,
|
||||||
fetch2?: Function,
|
fetch2?: Function,
|
||||||
depends?: Function
|
depends?: Function,
|
||||||
|
page: number = 1
|
||||||
): Promise<string | Post[] | null> {
|
): Promise<string | Post[] | null> {
|
||||||
if (depends) depends('post:respuestas');
|
if (depends) depends('post:respuestas');
|
||||||
const fetchFn = fetch2 ? fetch2 : fetch;
|
const fetchFn = fetch2 ? fetch2 : fetch;
|
||||||
try {
|
try {
|
||||||
const req = await fetchFn(`${get(apiBase)}/api/posts/${id}/replies`, {
|
const req = await fetchFn(`${get(apiBase)}/api/posts/${id}/replies?page=${page}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
Authorization: `Bearer ${get(sesionStore)?.accessToken || ''}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (req.ok) {
|
if (req.ok) {
|
||||||
|
|||||||
@@ -1,22 +1,28 @@
|
|||||||
import { sesionStore } from '@/stores/usuario';
|
import { sesionStore } from '@/stores/usuario';
|
||||||
import type { UsersResponseDto } from '../../types';
|
import type { UsersResponseDto } from '../../types';
|
||||||
import { get } from 'svelte/store';
|
|
||||||
import { apiBase } from '@/stores/url';
|
import { apiBase } from '@/stores/url';
|
||||||
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
export async function obtenerSeguidoresPorUsuario(
|
export async function obtenerSeguidoresPorUsuario(
|
||||||
id: string,
|
id: string,
|
||||||
|
page: number = 1,
|
||||||
limit: number = 20,
|
limit: number = 20,
|
||||||
fetch2: Function
|
fetch2?: Function
|
||||||
): Promise<UsersResponseDto | null> {
|
): Promise<UsersResponseDto | null> {
|
||||||
try {
|
try {
|
||||||
const fetchFunc = fetch2 || fetch;
|
const fetchFunc = fetch2 || fetch;
|
||||||
const response = await fetchFunc(`${get(apiBase)}/api/users/${id}/followers?limit=${limit}`, {
|
const skip = (page - 1) * limit;
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
const response = await fetchFunc(
|
||||||
'Content-Type': 'application/json',
|
`${get(apiBase)}/api/users/${id}/followers?skip=${skip}&limit=${limit}`,
|
||||||
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
{
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -5,26 +5,31 @@ import { get } from 'svelte/store';
|
|||||||
|
|
||||||
export async function obtenerSeguidosPorUsuario(
|
export async function obtenerSeguidosPorUsuario(
|
||||||
id: string,
|
id: string,
|
||||||
|
page: number = 1,
|
||||||
limit: number = 20,
|
limit: number = 20,
|
||||||
fetch2?: Function
|
fetch2?: Function
|
||||||
): Promise<UsersResponseDto | null> {
|
): Promise<UsersResponseDto | null> {
|
||||||
try {
|
try {
|
||||||
const fetchFunc = fetch2 || fetch;
|
const fetchFunc = fetch2 || fetch;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
const response = await fetchFunc(`${get(apiBase)}/api/users/${id}/following?limit=${limit}`, {
|
const response = await fetchFunc(
|
||||||
method: 'GET',
|
`${get(apiBase)}/api/users/${id}/following?skip=${skip}&limit=${limit}`,
|
||||||
headers: {
|
{
|
||||||
'Content-Type': 'application/json',
|
method: 'GET',
|
||||||
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const users: UsersResponseDto = await response.json();
|
const data: UsersResponseDto = await response.json();
|
||||||
return users;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
import { addPost } from "@/stores/posts";
|
import { addPost } from '@/stores/posts';
|
||||||
import { apiBase } from "@/stores/url";
|
import { apiBase } from '@/stores/url';
|
||||||
import { sesionStore } from "@/stores/usuario";
|
import { sesionStore } from '@/stores/usuario';
|
||||||
import { get } from "svelte/store";
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
export async function publicarPost(formData: FormData){
|
export async function publicarPost(formData: FormData) {
|
||||||
try{
|
try {
|
||||||
const req = fetch(get(apiBase) + '/api/posts', {
|
const req = fetch(get(apiBase) + '/api/posts', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
//credentials: 'include',
|
//credentials: 'include',
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
||||||
},
|
},
|
||||||
body: formData
|
body: formData
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await req;
|
const res = await req;
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const post = await res.json();
|
const post = await res.json();
|
||||||
addPost(post);
|
addPost(post);
|
||||||
return '';
|
return '';
|
||||||
}
|
|
||||||
return 'No se pudo crear el post.';
|
|
||||||
} catch {
|
|
||||||
return 'Fallo al alcanzar el servidor';
|
|
||||||
}
|
}
|
||||||
|
return 'No se pudo crear el post.';
|
||||||
|
} catch {
|
||||||
|
return 'Fallo al alcanzar el servidor';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
export async function updateImagenDePerfil(){
|
export async function updateImagenDePerfil() {
|
||||||
try{
|
try {
|
||||||
|
} catch {}
|
||||||
}catch{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import { sesionStore } from '@/stores/usuario';
|
|||||||
|
|
||||||
export async function updatePost(post: Post, callbackfn: Function, message: string) {
|
export async function updatePost(post: Post, callbackfn: Function, message: string) {
|
||||||
try {
|
try {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("content", post.content);
|
formData.append('content', post.content);
|
||||||
formData.append("image", post.image||"");
|
formData.append('image', post.image || '');
|
||||||
|
|
||||||
const req = await fetch(get(apiBase) + `/api/posts/${post.id}`, {
|
const req = await fetch(get(apiBase) + `/api/posts/${post.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
|
|||||||
134
src/lib/stores/cacheSeguidos.js
Normal file
134
src/lib/stores/cacheSeguidos.js
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
import { browser } from '$app/environment';
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
class FollowCache {
|
||||||
|
constructor() {
|
||||||
|
if (browser) {
|
||||||
|
this.loadFromStorage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {Map<string, boolean | Promise<boolean>>} */
|
||||||
|
#cache = new Map();
|
||||||
|
|
||||||
|
/** @type {import('svelte/store').Writable<Map<string, boolean>>} */
|
||||||
|
store = writable(new Map());
|
||||||
|
|
||||||
|
/** @param {string} userId */
|
||||||
|
get(userId) {
|
||||||
|
const value = this.#cache.get(userId);
|
||||||
|
return value instanceof Promise ? undefined : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {string} userId */
|
||||||
|
has(userId) {
|
||||||
|
return this.#cache.has(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} userId
|
||||||
|
* @param {() => Promise<boolean>} fetchFn
|
||||||
|
*/
|
||||||
|
async getOrFetch(userId, fetchFn) {
|
||||||
|
const existing = this.#cache.get(userId);
|
||||||
|
|
||||||
|
if (existing !== undefined) {
|
||||||
|
if (existing instanceof Promise) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const promise = fetchFn()
|
||||||
|
.then((result) => {
|
||||||
|
this.#setFinal(userId, result);
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.#cache.delete(userId);
|
||||||
|
this.#updateStore();
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.#cache.set(userId, promise);
|
||||||
|
this.#updateStore();
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} userId
|
||||||
|
* @param {boolean} isFollowed
|
||||||
|
*/
|
||||||
|
set(userId, isFollowed) {
|
||||||
|
this.#setFinal(userId, isFollowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} userId
|
||||||
|
* @param {boolean} value
|
||||||
|
*/
|
||||||
|
#setFinal(userId, value) {
|
||||||
|
this.#cache.set(userId, value);
|
||||||
|
this.#updateStore();
|
||||||
|
this.saveToStorage();
|
||||||
|
|
||||||
|
if (browser) {
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent('followCacheUpdated', {
|
||||||
|
detail: { userId, isFollowed: value }
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#updateStore() {
|
||||||
|
const filtered = Array.from(this.#cache.entries())
|
||||||
|
.filter(([_, v]) => typeof v === 'boolean');
|
||||||
|
|
||||||
|
this.store.set(
|
||||||
|
/** @type {Map<string, boolean>} */
|
||||||
|
(new Map(filtered))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {string} userId */
|
||||||
|
delete(userId) {
|
||||||
|
this.#cache.delete(userId);
|
||||||
|
this.#updateStore();
|
||||||
|
this.saveToStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.#cache.clear();
|
||||||
|
this.store.set(new Map());
|
||||||
|
this.saveToStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
saveToStorage() {
|
||||||
|
if (!browser) return;
|
||||||
|
const filtered = Array.from(this.#cache.entries())
|
||||||
|
.filter(([_, v]) => typeof v === 'boolean');
|
||||||
|
|
||||||
|
const data = Object.fromEntries(filtered);
|
||||||
|
sessionStorage.setItem('follow-cache', JSON.stringify(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
loadFromStorage() {
|
||||||
|
if (!browser) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stored = sessionStorage.getItem('follow-cache');
|
||||||
|
if (!stored) return;
|
||||||
|
const data = JSON.parse(stored);
|
||||||
|
|
||||||
|
this.#cache = new Map(Object.entries(data));
|
||||||
|
this.#updateStore();
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error cargando follow-cache:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cacheSeguidos = new FollowCache();
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
import { browser } from '$app/environment';
|
|
||||||
import { writable } from 'svelte/store';
|
|
||||||
|
|
||||||
class FollowCache {
|
|
||||||
constructor() {
|
|
||||||
if (browser) {
|
|
||||||
this.loadFromStorage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @type {Map<string, boolean>} */
|
|
||||||
#cache = new Map();
|
|
||||||
|
|
||||||
/** @type {import('svelte/store').Writable<Map<string, boolean>>} */
|
|
||||||
store = writable(this.#cache);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} userId
|
|
||||||
* @returns {boolean | undefined}
|
|
||||||
*/
|
|
||||||
get(userId) {
|
|
||||||
return this.#cache.get(userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} userId
|
|
||||||
* @param {boolean} isFollowed
|
|
||||||
*/
|
|
||||||
set(userId, isFollowed) {
|
|
||||||
this.#cache.set(userId, isFollowed);
|
|
||||||
this.store.set(this.#cache);
|
|
||||||
this.saveToStorage();
|
|
||||||
|
|
||||||
if (browser) {
|
|
||||||
window.dispatchEvent(
|
|
||||||
new CustomEvent('followCacheUpdated', {
|
|
||||||
detail: { userId, isFollowed }
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} userId
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
has(userId) {
|
|
||||||
return this.#cache.has(userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} userId
|
|
||||||
*/
|
|
||||||
delete(userId) {
|
|
||||||
this.#cache.delete(userId);
|
|
||||||
this.store.set(this.#cache);
|
|
||||||
this.saveToStorage();
|
|
||||||
|
|
||||||
if (browser) {
|
|
||||||
window.dispatchEvent(
|
|
||||||
new CustomEvent('followCacheUpdated', {
|
|
||||||
detail: { userId, isFollowed: false }
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
this.#cache.clear();
|
|
||||||
this.store.set(this.#cache);
|
|
||||||
this.saveToStorage();
|
|
||||||
|
|
||||||
if (browser) {
|
|
||||||
window.dispatchEvent(
|
|
||||||
new CustomEvent('followCacheUpdated', {
|
|
||||||
detail: { clearAll: true }
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
saveToStorage() {
|
|
||||||
if (browser) {
|
|
||||||
const data = Object.fromEntries(this.#cache);
|
|
||||||
sessionStorage.setItem('follow-cache', JSON.stringify(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadFromStorage() {
|
|
||||||
if (browser) {
|
|
||||||
try {
|
|
||||||
const stored = sessionStorage.getItem('follow-cache');
|
|
||||||
if (stored) {
|
|
||||||
const data = JSON.parse(stored);
|
|
||||||
this.#cache = new Map(Object.entries(data));
|
|
||||||
this.store.set(this.#cache);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error cargando desde sesion:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const cacheSeguidos = new FollowCache();
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
import type { Post } from '../../types';
|
import type { Post } from '../../types';
|
||||||
|
|
||||||
export const posts = writable<Post[]>([]);
|
export const posts = writable<Post[] | null>(null);
|
||||||
export const loadingPosts = writable(false);
|
export const loadingPosts = writable(false);
|
||||||
export const page = writable(1);
|
export const page = writable(1);
|
||||||
|
|
||||||
@@ -19,21 +19,14 @@ export const addPost = (post: Post) => {
|
|||||||
posts.update((current) => [post, ...current]);
|
posts.update((current) => [post, ...current]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updatePostStore = (
|
export const updatePostStore = (postId: string, updatedData: Partial<Post>) => {
|
||||||
postId: string,
|
|
||||||
updatedData: Partial<Post>
|
|
||||||
) => {
|
|
||||||
posts.update((current) =>
|
posts.update((current) =>
|
||||||
current.map((post) =>
|
current.map((post) => (post.id === postId ? { ...post, ...updatedData } : post))
|
||||||
post.id === postId ? { ...post, ...updatedData } : post
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const removePost = (postId: string) => {
|
export const removePost = (postId: string) => {
|
||||||
posts.update((current) =>
|
posts.update((current) => current.filter((post) => post.id !== postId));
|
||||||
current.filter((post) => post.id !== postId)
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const resetPosts = () => {
|
export const resetPosts = () => {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { get, writable } from 'svelte/store';
|
|||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import type { Sesion } from '../../types';
|
import type { Sesion } from '../../types';
|
||||||
import { apiBase } from '@/stores/url';
|
import { apiBase } from '@/stores/url';
|
||||||
|
import { getFirebaseApp, getFirebaseAuth } from './firebase';
|
||||||
|
|
||||||
const { subscribe } = apiBase;
|
const { subscribe } = apiBase;
|
||||||
let baseUrl: string = '';
|
let baseUrl: string = '';
|
||||||
@@ -21,9 +22,9 @@ export const sesionStore = {
|
|||||||
reset: () => currentSesion.set(null)
|
reset: () => currentSesion.set(null)
|
||||||
};
|
};
|
||||||
|
|
||||||
sesionStore.subscribe((value) => {
|
// sesionStore.subscribe((value) => {
|
||||||
console.log(value);
|
// console.log(value);
|
||||||
});
|
// });
|
||||||
|
|
||||||
if (browser) {
|
if (browser) {
|
||||||
currentSesion.subscribe((value) => {
|
currentSesion.subscribe((value) => {
|
||||||
@@ -54,13 +55,13 @@ if (browser) {
|
|||||||
|
|
||||||
if (sesion.isFirebase) {
|
if (sesion.isFirebase) {
|
||||||
try {
|
try {
|
||||||
// Simulamos la importación dinámica de Firebase
|
getFirebaseApp();
|
||||||
const { getAuth } = await import('firebase/auth');
|
|
||||||
const auth = getAuth();
|
const auth = getFirebaseAuth();
|
||||||
const user = auth.currentUser;
|
const user = auth.currentUser;
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
const token = await user.getIdToken(true);
|
const token = await user.getIdToken();
|
||||||
currentSesion.update((s) => {
|
currentSesion.update((s) => {
|
||||||
if (s) {
|
if (s) {
|
||||||
return { ...s, accessToken: token };
|
return { ...s, accessToken: token };
|
||||||
@@ -71,6 +72,7 @@ if (browser) {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error obteniendo token de Firebase:', error);
|
console.error('Error obteniendo token de Firebase:', error);
|
||||||
|
sesionStore.reset();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,8 +82,14 @@ if (browser) {
|
|||||||
|
|
||||||
const expirationTime = decoded.exp * 1000;
|
const expirationTime = decoded.exp * 1000;
|
||||||
const currentTime = Date.now();
|
const currentTime = Date.now();
|
||||||
const timeUntilExpiration = expirationTime - currentTime;
|
|
||||||
|
|
||||||
|
// Si el token ya expiró, hacer reset
|
||||||
|
if (expirationTime < currentTime) {
|
||||||
|
sesionStore.reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeUntilExpiration = expirationTime - currentTime;
|
||||||
return timeUntilExpiration <= 60 * 1000; // 1 minuto
|
return timeUntilExpiration <= 60 * 1000; // 1 minuto
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -116,7 +124,7 @@ if (browser) {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error refrescando token:', error);
|
console.error('Error refrescando token:', error);
|
||||||
currentSesion.set(null);
|
sesionStore.reset();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
|
|||||||
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };
|
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };
|
||||||
|
|
||||||
export function filtrarImagen(file: File) {
|
export function filtrarImagen(file: File) {
|
||||||
if (file) {
|
if (file) {
|
||||||
const allowed = ['image/png', 'image/jpg', 'image/jpeg', 'image/gif', 'image/webp'];
|
const allowed = ['image/png', 'image/jpg', 'image/jpeg', 'image/gif', 'image/webp'];
|
||||||
if (allowed.includes(file.type)) {
|
if (allowed.includes(file.type)) {
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import CardContent from '@/components/ui/card/card-content.svelte';
|
import CardContent from '@/components/ui/card/card-content.svelte';
|
||||||
import Card from '@/components/ui/card/card.svelte';
|
import Card from '@/components/ui/card/card.svelte';
|
||||||
import CardDescription from '@/components/ui/card/card-description.svelte';
|
|
||||||
import { page } from '$app/state';
|
|
||||||
import TablaUsuarios from '@/components/TablaUsuarios.svelte';
|
import TablaUsuarios from '@/components/TablaUsuarios.svelte';
|
||||||
import CardTitle from '@/components/ui/card/card-title.svelte';
|
import CardTitle from '@/components/ui/card/card-title.svelte';
|
||||||
import CardHeader from '@/components/ui/card/card-header.svelte';
|
import CardHeader from '@/components/ui/card/card-header.svelte';
|
||||||
@@ -10,7 +8,8 @@
|
|||||||
|
|
||||||
interface Prop {
|
interface Prop {
|
||||||
data: {
|
data: {
|
||||||
usuarios?: UserResponseDto[];
|
usuarios: UserResponseDto[];
|
||||||
|
hayMas: boolean;
|
||||||
error: boolean;
|
error: boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -29,11 +28,7 @@
|
|||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{#if data.usuarios?.length === 0}
|
<TablaUsuarios usuarios={data.usuarios} hayMas={data.hayMas}></TablaUsuarios>
|
||||||
<CardDescription>No hay usuarios que mostar</CardDescription>
|
|
||||||
{:else}
|
|
||||||
<TablaUsuarios usuarios={data.usuarios || []}></TablaUsuarios>
|
|
||||||
{/if}
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,28 +1,27 @@
|
|||||||
import { apiBase } from '@/stores/url.js';
|
import { busquedaAdminUsuarios } from '@/hooks/busquedaAdminUsuarios.js';
|
||||||
import { sesionStore } from '@/stores/usuario';
|
import type { PageLoad } from './$types.js';
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { fetchUsuariosAdmin } from '@/hooks/UsuariosAdmin.js';
|
||||||
import { get } from 'svelte/store';
|
|
||||||
import type { UserResponseDto } from '../../../types.js';
|
|
||||||
|
|
||||||
export const ssr = false;
|
export const ssr = false;
|
||||||
|
|
||||||
export async function load({ depends, fetch }) {
|
export const load: PageLoad = async ({ depends, fetch }) => {
|
||||||
depends('admin:load');
|
depends('admin:load');
|
||||||
const response = await fetch(get(apiBase) + '/api/admin/users', {
|
let url = new URL(location.href);
|
||||||
method: 'GET',
|
let query = url.searchParams.get('q') ?? '';
|
||||||
headers: {
|
let page = Number(url.searchParams.get('p'));
|
||||||
'Content-Type': 'application/json',
|
if (isNaN(page) || page < 1) {
|
||||||
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
page = 1;
|
||||||
}
|
|
||||||
});
|
|
||||||
if (response.status === 401) {
|
|
||||||
throw redirect(302, '/');
|
|
||||||
}
|
}
|
||||||
if (!response.ok) {
|
|
||||||
|
const result = await busquedaAdminUsuarios(query, 5, page, fetch);
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
return { error: true };
|
return { error: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
const usuarios: UserResponseDto[] = await response.json();
|
return {
|
||||||
|
usuarios: result.usuarios,
|
||||||
return { usuarios, error: false };
|
hayMas: result.hayMas,
|
||||||
}
|
error: false
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import { ModeWatcher } from 'mode-watcher';
|
import { ModeWatcher } from 'mode-watcher';
|
||||||
import Header from '@/head/Header.svelte';
|
import Header from '@/head/Header.svelte';
|
||||||
import { TooltipProvider } from '@/components/ui/tooltip';
|
import { TooltipProvider } from '@/components/ui/tooltip';
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
</script>
|
</script>
|
||||||
@@ -17,3 +18,11 @@
|
|||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
<footer
|
||||||
|
class="fixed right-0 bottom-0 left-0 border-t bg-background p-2 text-center text-sm text-muted-foreground"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
© {new Date().getFullYear()} Mini X. Todos los derechos reservados.
|
||||||
|
<a class="text-blue-500 underline" href={resolve('/about')}>Sobre Nosotros</a>
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { replaceState } from '$app/navigation';
|
import { replaceState } from '$app/navigation';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import Card from '@/components/ui/card/card.svelte';
|
import Card from '@/components/ui/card/card.svelte';
|
||||||
import { Content } from '@/components/ui/card';
|
import { Content } from '@/components/ui/card';
|
||||||
import Spinner from '@/components/ui/spinner/spinner.svelte';
|
import Spinner from '@/components/ui/spinner/spinner.svelte';
|
||||||
@@ -11,21 +10,20 @@
|
|||||||
import PostCard from '@/components/PostCard.svelte';
|
import PostCard from '@/components/PostCard.svelte';
|
||||||
import ModalEditar from './[perfil]/modalEditar.svelte';
|
import ModalEditar from './[perfil]/modalEditar.svelte';
|
||||||
import { sesionStore } from '@/stores/usuario';
|
import { sesionStore } from '@/stores/usuario';
|
||||||
import {
|
import { posts, updatePostStore, loadingPosts, resetPosts } from '@/stores/posts';
|
||||||
posts,
|
|
||||||
updatePostStore,
|
|
||||||
loadingPosts
|
|
||||||
} from '@/stores/posts';
|
|
||||||
import { updatePost } from '@/hooks/updatePost';
|
import { updatePost } from '@/hooks/updatePost';
|
||||||
import { loadMorePosts } from '@/hooks/loadMorePosts';
|
import { loadMorePosts } from '@/hooks/loadMorePosts';
|
||||||
import type { Post } from '../types';
|
import type { Post } from '../types';
|
||||||
import { fade, slide } from 'svelte/transition';
|
import { fade, slide } from 'svelte/transition';
|
||||||
|
|
||||||
let postAModificar: Post | null = null;
|
let postAModificar: Post | null = $state(null);
|
||||||
let mensajeError = '';
|
let mensajeError = $state('');
|
||||||
let sentinel: HTMLDivElement;
|
let sentinel: HTMLDivElement;
|
||||||
|
|
||||||
onMount(() => {
|
// $inspect($posts);
|
||||||
|
|
||||||
|
resetPosts();
|
||||||
|
$effect(() => {
|
||||||
loadMorePosts();
|
loadMorePosts();
|
||||||
|
|
||||||
const observer = new IntersectionObserver(
|
const observer = new IntersectionObserver(
|
||||||
@@ -47,8 +45,7 @@
|
|||||||
|
|
||||||
await updatePost(
|
await updatePost(
|
||||||
postAModificar,
|
postAModificar,
|
||||||
(postNuevo: Post) =>
|
(postNuevo: Post) => updatePostStore(postAModificar!.id, postNuevo),
|
||||||
updatePostStore(postAModificar!.id, postNuevo),
|
|
||||||
mensajeError
|
mensajeError
|
||||||
);
|
);
|
||||||
postAModificar = null;
|
postAModificar = null;
|
||||||
@@ -63,9 +60,7 @@
|
|||||||
|
|
||||||
{#if from === 'cambio_contraseña'}
|
{#if from === 'cambio_contraseña'}
|
||||||
<Dialog open>
|
<Dialog open>
|
||||||
<DialogContent>
|
<DialogContent>Se cambió la contraseña del usuario exitosamente</DialogContent>
|
||||||
Se cambió la contraseña del usuario exitosamente
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@@ -85,22 +80,13 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
{#if $posts.length === 0 && $loadingPosts}
|
{#if $posts === null || $posts.length === 0}
|
||||||
<Card>
|
<Card>
|
||||||
<Content class="flex items-center justify-center gap-2">
|
<Content class="flex items-center justify-center gap-2">
|
||||||
<Spinner class="h-10 w-10" />
|
<Spinner class="h-10 w-10" />
|
||||||
<p>Cargando</p>
|
<p>Cargando</p>
|
||||||
</Content>
|
</Content>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{:else if $posts.length === 0}
|
|
||||||
<Card>
|
|
||||||
<Content>
|
|
||||||
<p class="text-center leading-7">
|
|
||||||
No hay Posts que mostrar
|
|
||||||
</p>
|
|
||||||
</Content>
|
|
||||||
</Card>
|
|
||||||
{:else}
|
{:else}
|
||||||
{#each $posts as post (post.id)}
|
{#each $posts as post (post.id)}
|
||||||
<div transition:slide>
|
<div transition:slide>
|
||||||
@@ -111,7 +97,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div bind:this={sentinel} class="h-1"></div>
|
<div bind:this={sentinel} class="h-1"></div>
|
||||||
|
|
||||||
{#if $loadingPosts && $posts.length > 0}
|
{#if $loadingPosts && $posts!.length > 0}
|
||||||
<div class="flex justify-center py-4">
|
<div class="flex justify-center py-4">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
@@ -120,9 +106,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{#if postAModificar}
|
{#if postAModificar}
|
||||||
<div in:fade>
|
<div in:fade>
|
||||||
<ModalEditar
|
<ModalEditar callbackfn={handleEditar} bind:post={postAModificar} />
|
||||||
callbackfn={handleEditar}
|
|
||||||
bind:post={postAModificar}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -1 +1,4 @@
|
|||||||
|
import { resetPosts } from '@/stores/posts';
|
||||||
|
|
||||||
//export const ssr = true;
|
//export const ssr = true;
|
||||||
|
// export async function load({}) {}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { apiBase } from '@/stores/url';
|
import { apiBase } from '@/stores/url';
|
||||||
import PenLine from '@lucide/svelte/icons/pen-line';
|
|
||||||
import type { Post } from '../../types.js';
|
import type { Post } from '../../types.js';
|
||||||
import { fade, slide } from 'svelte/transition';
|
import { fade, slide } from 'svelte/transition';
|
||||||
import PostCard from '@/components/PostCard.svelte';
|
import PostCard from '@/components/PostCard.svelte';
|
||||||
@@ -9,7 +8,7 @@
|
|||||||
import ModalEditar from './modalEditar.svelte';
|
import ModalEditar from './modalEditar.svelte';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import Button from '@/components/ui/button/button.svelte';
|
import Button from '@/components/ui/button/button.svelte';
|
||||||
import { Dialog } from '@/components/ui/dialog/index.js';
|
import { Dialog } from '@/components/ui/dialog';
|
||||||
import CrearPost from '@/components/crear-post.svelte';
|
import CrearPost from '@/components/crear-post.svelte';
|
||||||
import DialogContent from '@/components/ui/dialog/dialog-content.svelte';
|
import DialogContent from '@/components/ui/dialog/dialog-content.svelte';
|
||||||
import DialogTitle from '@/components/ui/dialog/dialog-title.svelte';
|
import DialogTitle from '@/components/ui/dialog/dialog-title.svelte';
|
||||||
@@ -19,54 +18,108 @@
|
|||||||
import CardPerfil from '@/components/CardPerfil.svelte';
|
import CardPerfil from '@/components/CardPerfil.svelte';
|
||||||
import DialogModificarUsuario from '@/components/DialogModificarUsuario.svelte';
|
import DialogModificarUsuario from '@/components/DialogModificarUsuario.svelte';
|
||||||
import BotonSeguir from '@/components/BotonSeguir.svelte';
|
import BotonSeguir from '@/components/BotonSeguir.svelte';
|
||||||
import UserPen from '@lucide/svelte/icons/user-pen';
|
|
||||||
import DialogResetPassword from '@/components/DialogResetPassword.svelte';
|
import DialogResetPassword from '@/components/DialogResetPassword.svelte';
|
||||||
import Key from '@lucide/svelte/icons/key';
|
import Key from '@lucide/svelte/icons/key';
|
||||||
|
import UserPen from '@lucide/svelte/icons/user-pen';
|
||||||
|
import PenLine from '@lucide/svelte/icons/pen-line';
|
||||||
|
|
||||||
let { params } = $props();
|
let { params } = $props();
|
||||||
|
|
||||||
let cargando = $state(true);
|
setPosts([]);
|
||||||
|
|
||||||
|
let cargando = $state(false);
|
||||||
|
let finished = $state(false);
|
||||||
|
let pageNumber = $state(1);
|
||||||
|
let sentinel = $state<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
let mensajeError = $state('');
|
let mensajeError = $state('');
|
||||||
let postAModificar: Post | null = $state(null);
|
let postAModificar: Post | null = $state(null);
|
||||||
|
|
||||||
let showCrearPost = $state(false);
|
let showCrearPost = $state(false);
|
||||||
|
|
||||||
let data = $derived(page.data);
|
let data = $derived(page.data);
|
||||||
$inspect(data);
|
// $inspect(data);
|
||||||
|
|
||||||
$effect(() => {
|
let fetching = false;
|
||||||
obtenerPosts();
|
// svelte-ignore state_referenced_locally
|
||||||
});
|
let currentProfile = $state(params.perfil);
|
||||||
|
|
||||||
async function obtenerPosts() {
|
async function obtenerPosts() {
|
||||||
|
if (fetching || finished) return;
|
||||||
|
|
||||||
|
fetching = true;
|
||||||
|
cargando = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const req = await fetch($apiBase + '/api/posts/user/' + params.perfil, {
|
const res = await fetch(
|
||||||
method: 'GET',
|
`${$apiBase}/api/posts/user/${params.perfil}?page=${pageNumber}&pageSize=20`,
|
||||||
headers: {
|
{
|
||||||
Authorization: `Bearer ${$sesionStore?.accessToken}`
|
headers: {
|
||||||
|
Authorization: `Bearer ${$sesionStore?.accessToken || ''}`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
if (req.ok) {
|
const nuevosPosts: Post[] = await res.json();
|
||||||
setPosts(await req.json());
|
|
||||||
|
if (nuevosPosts.length === 0) {
|
||||||
|
finished = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mensajeError = 'Fallo al obtener los datos';
|
|
||||||
} catch {
|
posts.update((actuales = []) => [...actuales, ...nuevosPosts]);
|
||||||
mensajeError = 'No se alcanzo el servidor';
|
|
||||||
|
pageNumber++;
|
||||||
|
|
||||||
|
if (nuevosPosts.length < 20) {
|
||||||
|
finished = true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
mensajeError = 'Error al cargar los posts';
|
||||||
} finally {
|
} finally {
|
||||||
|
fetching = false;
|
||||||
cargando = false;
|
cargando = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (currentProfile !== params.perfil) {
|
||||||
|
currentProfile = params.perfil;
|
||||||
|
setPosts([]);
|
||||||
|
pageNumber = 1;
|
||||||
|
finished = false;
|
||||||
|
mensajeError = '';
|
||||||
|
fetching = false;
|
||||||
|
|
||||||
|
obtenerPosts();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!sentinel || finished) return;
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
if (entry.isIntersecting && !fetching && !finished) {
|
||||||
|
obtenerPosts();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ rootMargin: '100px' }
|
||||||
|
);
|
||||||
|
|
||||||
|
observer.observe(sentinel);
|
||||||
|
|
||||||
|
return () => observer.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
async function handleEditar(e: SubmitEvent) {
|
async function handleEditar(e: SubmitEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (postAModificar == null) return;
|
if (!postAModificar) return;
|
||||||
|
|
||||||
await updatePost(
|
await updatePost(
|
||||||
postAModificar,
|
postAModificar,
|
||||||
(postnuevo: Post) => updatePostStore(postAModificar!.id, postnuevo),
|
(postNuevo: Post) => updatePostStore(postAModificar!.id, postNuevo),
|
||||||
|
|
||||||
mensajeError
|
mensajeError
|
||||||
);
|
);
|
||||||
|
|
||||||
postAModificar = null;
|
postAModificar = null;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -74,9 +127,9 @@
|
|||||||
<!-- {$inspect(data)} -->
|
<!-- {$inspect(data)} -->
|
||||||
<div class="flex min-h-fit w-full items-center justify-center p-6 md:p-10">
|
<div class="flex min-h-fit w-full items-center justify-center p-6 md:p-10">
|
||||||
<div class="w-full max-w-6xl">
|
<div class="w-full max-w-6xl">
|
||||||
{#key data}
|
<!-- {#key data.id} -->
|
||||||
<CardPerfil bind:data />
|
<CardPerfil bind:data />
|
||||||
{/key}
|
<!-- {/key} -->
|
||||||
<h1
|
<h1
|
||||||
class="mt-10 flex scroll-m-20 justify-between text-3xl font-extrabold tracking-tight lg:text-3xl"
|
class="mt-10 flex scroll-m-20 justify-between text-3xl font-extrabold tracking-tight lg:text-3xl"
|
||||||
>
|
>
|
||||||
@@ -93,14 +146,12 @@
|
|||||||
<PenLine />
|
<PenLine />
|
||||||
</Button>
|
</Button>
|
||||||
{:else if $posts?.length == 0}
|
{:else if $posts?.length == 0}
|
||||||
<BotonSeguir post={{ authorId: data.id }} />
|
<BotonSeguir post={{ authorId: data.id, id: data.id }} />
|
||||||
{/if}
|
{/if}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<hr class="mb-8" />
|
<hr class="mb-8" />
|
||||||
{#if cargando}
|
{#if mensajeError !== ''}
|
||||||
<CardCargando />
|
|
||||||
{:else if mensajeError !== ''}
|
|
||||||
<CardError {mensajeError} />
|
<CardError {mensajeError} />
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
@@ -109,6 +160,16 @@
|
|||||||
<PostCard {post} bind:postAModificar />
|
<PostCard {post} bind:postAModificar />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
<div bind:this={sentinel} class="h-1"></div>
|
||||||
|
|
||||||
|
{#if cargando && !finished}
|
||||||
|
<CardCargando />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if finished && $posts.length === 0}
|
||||||
|
<p class="text-center text-muted-foreground">No hay posts para mostrar</p>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -135,7 +196,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if $sesionStore?.isAdmin || $sesionStore?.username == params.perfil}
|
{#if $sesionStore?.isAdmin || $sesionStore?.username == params.perfil}
|
||||||
<div class="fixed right-8 bottom-8 flex flex-col gap-2">
|
<div class="fixed right-8 bottom-12 flex flex-col gap-2">
|
||||||
<DialogModificarUsuario bind:data>
|
<DialogModificarUsuario bind:data>
|
||||||
<Button variant="default" size="icon-lg">
|
<Button variant="default" size="icon-lg">
|
||||||
<UserPen />
|
<UserPen />
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
import { obtenerUsuarioPorUsername } from '@/hooks/obtenerUsuario.js';
|
import { obtenerUsuarioPorUsername } from '@/hooks/obtenerUsuario.js';
|
||||||
import type { User, UserResponseDto } from '../../types.js';
|
import type { UserResponseDto } from '../../types.js';
|
||||||
import { error } from '@sveltejs/kit';
|
import { error } from '@sveltejs/kit';
|
||||||
|
import type { PageLoad } from './$types';
|
||||||
import { obtenerSeguidosPorUsuario } from '@/hooks/obtenerSeguidosPorUsuario.js';
|
import { obtenerSeguidosPorUsuario } from '@/hooks/obtenerSeguidosPorUsuario.js';
|
||||||
import { obtenerSeguidoresPorUsuario } from '@/hooks/obtenerSeguidoresPorUsuario.js';
|
import { obtenerSeguidoresPorUsuario } from '@/hooks/obtenerSeguidoresPorUsuario.js';
|
||||||
import { obtenerCantidadDeSeguidores } from '@/hooks/obtenerCantidadDeSeguidores.js';
|
import { obtenerCantidadDeSeguidores } from '@/hooks/obtenerCantidadDeSeguidores.js';
|
||||||
import { obtenerCantidadDeSeguidos } from '@/hooks/obtenerCantidadDeSeguidos.js';
|
import { obtenerCantidadDeSeguidos } from '@/hooks/obtenerCantidadDeSeguidos.js';
|
||||||
|
|
||||||
export async function load({ params, depends, fetch }) {
|
export const load: PageLoad = async ({ params, depends, fetch }) => {
|
||||||
depends('perfil:general');
|
depends('perfil:general');
|
||||||
const usuario: UserResponseDto | null = await obtenerUsuarioPorUsername(params.perfil, fetch);
|
const usuario: UserResponseDto | null = await obtenerUsuarioPorUsername(params.perfil, fetch);
|
||||||
if (!usuario) error(404, 'No se encontro el usuario, ' + params.perfil);
|
if (!usuario) error(404, 'No se encontro el usuario, ' + params.perfil);
|
||||||
|
|
||||||
const [seguidos, seguidores, countSeguidores, countSeguidos] = await Promise.all([
|
const [seguidos, seguidores, countSeguidores, countSeguidos] = await Promise.all([
|
||||||
obtenerSeguidosPorUsuario(usuario.id, 5, fetch),
|
obtenerSeguidosPorUsuario(usuario.id, 1, 5, fetch),
|
||||||
obtenerSeguidoresPorUsuario(usuario.id, 5, fetch),
|
obtenerSeguidoresPorUsuario(usuario.id, 1, 5, fetch),
|
||||||
obtenerCantidadDeSeguidores(usuario.id, fetch),
|
obtenerCantidadDeSeguidores(usuario.id, fetch),
|
||||||
obtenerCantidadDeSeguidos(usuario.id, fetch)
|
obtenerCantidadDeSeguidos(usuario.id, fetch)
|
||||||
]);
|
]);
|
||||||
@@ -25,4 +26,4 @@ export async function load({ params, depends, fetch }) {
|
|||||||
countSeguidores: countSeguidores.count,
|
countSeguidores: countSeguidores.count,
|
||||||
countSeguidos: countSeguidos.count
|
countSeguidos: countSeguidos.count
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,15 +1,40 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ArrowLeft from '@lucide/svelte/icons/chevron-left';
|
import ArrowLeft from '@lucide/svelte/icons/chevron-left';
|
||||||
|
import ChevronLeft from '@lucide/svelte/icons/chevron-left';
|
||||||
|
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
||||||
import type { UserResponseDto } from '../../../types';
|
import type { UserResponseDto } from '../../../types';
|
||||||
import UserCard from '@/components/UserCard.svelte';
|
import UserCard from '@/components/UserCard.svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import { obtenerSeguidoresPorUsuario } from '@/hooks/obtenerSeguidoresPorUsuario';
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
usuario: UserResponseDto;
|
usuario: UserResponseDto;
|
||||||
seguidores: UserResponseDto[];
|
seguidores: UserResponseDto[];
|
||||||
|
totalCount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
let { data }: { data: Data } = $props();
|
let { data }: { data: Data } = $props();
|
||||||
|
|
||||||
|
let currentPage = $state(1);
|
||||||
|
let isLoading = $state(false);
|
||||||
|
const limit = 100;
|
||||||
|
|
||||||
|
let totalPages = $derived(Math.ceil(data.totalCount / limit));
|
||||||
|
|
||||||
|
async function loadPage(page: number) {
|
||||||
|
if (isLoading) return;
|
||||||
|
|
||||||
|
isLoading = true;
|
||||||
|
const response = await obtenerSeguidoresPorUsuario(data.usuario.id, page, limit);
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
data.seguidores = response.response as UserResponseDto[];
|
||||||
|
data.totalCount = response.totalCount;
|
||||||
|
currentPage = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading = false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex min-h-fit w-full items-center justify-center p-6 md:p-10">
|
<div class="flex min-h-fit w-full items-center justify-center p-6 md:p-10">
|
||||||
@@ -25,9 +50,14 @@
|
|||||||
<ArrowLeft />
|
<ArrowLeft />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{#if data.seguidores.length === 0}
|
|
||||||
|
{#if isLoading}
|
||||||
<div class="py-8 text-center text-muted-foreground">
|
<div class="py-8 text-center text-muted-foreground">
|
||||||
<p>No hay seguidores para mostrar.</p>
|
<p>Cargando...</p>
|
||||||
|
</div>
|
||||||
|
{:else if data.seguidores.length === 0}
|
||||||
|
<div class="py-8 text-center text-muted-foreground">
|
||||||
|
<p>No hay seguidos para mostrar.</p>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-col sm:grid" style="grid-template-columns: repeat(2, 1fr); gap: 1rem;">
|
<div class="flex flex-col sm:grid" style="grid-template-columns: repeat(2, 1fr); gap: 1rem;">
|
||||||
@@ -38,5 +68,29 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if totalPages > 1}
|
||||||
|
<div class="mt-6 flex items-center justify-center gap-2">
|
||||||
|
<button
|
||||||
|
class="rounded-md border bg-card p-2 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
onclick={() => loadPage(currentPage - 1)}
|
||||||
|
disabled={currentPage === 1 || isLoading}
|
||||||
|
>
|
||||||
|
<ChevronLeft class="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span class="px-4 text-sm text-muted-foreground">
|
||||||
|
Página {currentPage} de {totalPages}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="rounded-md border bg-card p-2 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
onclick={() => loadPage(currentPage + 1)}
|
||||||
|
disabled={currentPage === totalPages || isLoading}
|
||||||
|
>
|
||||||
|
<ChevronRight class="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import { obtenerSeguidoresPorUsuario } from '@/hooks/obtenerSeguidoresPorUsuario';
|
import { obtenerSeguidoresPorUsuario } from '@/hooks/obtenerSeguidoresPorUsuario';
|
||||||
import { obtenerUsuarioPorUsername } from '@/hooks/obtenerUsuario';
|
import { obtenerUsuarioPorUsername } from '@/hooks/obtenerUsuario';
|
||||||
import { error } from '@sveltejs/kit';
|
import { error } from '@sveltejs/kit';
|
||||||
|
import type { PageLoad } from './$types';
|
||||||
import type { UserResponseDto, UsersResponseDto } from '../../../types';
|
import type { UserResponseDto, UsersResponseDto } from '../../../types';
|
||||||
|
|
||||||
export async function load({ params, fetch }) {
|
export const load: PageLoad = async ({ params, fetch }) => {
|
||||||
const usuario: UserResponseDto | null = await obtenerUsuarioPorUsername(params.perfil, fetch);
|
const usuario: UserResponseDto | null = await obtenerUsuarioPorUsername(params.perfil, fetch);
|
||||||
if (!usuario) error(404, 'No se encontro el usuario, ' + params.perfil);
|
if (!usuario) error(404, 'No se encontro el usuario, ' + params.perfil);
|
||||||
|
|
||||||
const seguidoresResponse: UsersResponseDto | null = await obtenerSeguidoresPorUsuario(
|
const seguidoresResponse: UsersResponseDto | null = await obtenerSeguidoresPorUsuario(
|
||||||
usuario.id,
|
usuario.id,
|
||||||
|
1,
|
||||||
100,
|
100,
|
||||||
fetch
|
fetch
|
||||||
);
|
);
|
||||||
@@ -17,4 +19,4 @@ export async function load({ params, fetch }) {
|
|||||||
usuario,
|
usuario,
|
||||||
seguidores: seguidoresResponse?.response || []
|
seguidores: seguidoresResponse?.response || []
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,15 +1,40 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ArrowLeft from '@lucide/svelte/icons/chevron-left';
|
import ArrowLeft from '@lucide/svelte/icons/chevron-left';
|
||||||
|
import ChevronLeft from '@lucide/svelte/icons/chevron-left';
|
||||||
|
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
||||||
import type { UserResponseDto } from '../../../types';
|
import type { UserResponseDto } from '../../../types';
|
||||||
import UserCard from '@/components/UserCard.svelte';
|
import UserCard from '@/components/UserCard.svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import { obtenerSeguidosPorUsuario } from '@/hooks/obtenerSeguidosPorUsuario';
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
usuario: UserResponseDto;
|
usuario: UserResponseDto;
|
||||||
seguidos: UserResponseDto[];
|
seguidos: UserResponseDto[];
|
||||||
|
totalCount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
let { data }: { data: Data } = $props();
|
let { data }: { data: Data } = $props();
|
||||||
|
|
||||||
|
let currentPage = $state(1);
|
||||||
|
let isLoading = $state(false);
|
||||||
|
const limit = 100;
|
||||||
|
|
||||||
|
let totalPages = $derived(Math.ceil(data.totalCount / limit));
|
||||||
|
|
||||||
|
async function loadPage(page: number) {
|
||||||
|
if (isLoading) return;
|
||||||
|
|
||||||
|
isLoading = true;
|
||||||
|
const response = await obtenerSeguidosPorUsuario(data.usuario.id, page, limit);
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
data.seguidos = response.response as UserResponseDto[];
|
||||||
|
data.totalCount = response.totalCount;
|
||||||
|
currentPage = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading = false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex min-h-fit w-full items-center justify-center p-6 md:p-10">
|
<div class="flex min-h-fit w-full items-center justify-center p-6 md:p-10">
|
||||||
@@ -25,7 +50,12 @@
|
|||||||
<ArrowLeft />
|
<ArrowLeft />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{#if data.seguidos.length === 0}
|
|
||||||
|
{#if isLoading}
|
||||||
|
<div class="py-8 text-center text-muted-foreground">
|
||||||
|
<p>Cargando...</p>
|
||||||
|
</div>
|
||||||
|
{:else if data.seguidos.length === 0}
|
||||||
<div class="py-8 text-center text-muted-foreground">
|
<div class="py-8 text-center text-muted-foreground">
|
||||||
<p>No hay seguidos para mostrar.</p>
|
<p>No hay seguidos para mostrar.</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,5 +68,29 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if totalPages > 1}
|
||||||
|
<div class="mt-6 flex items-center justify-center gap-2">
|
||||||
|
<button
|
||||||
|
class="rounded-md border bg-card p-2 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
onclick={() => loadPage(currentPage - 1)}
|
||||||
|
disabled={currentPage === 1 || isLoading}
|
||||||
|
>
|
||||||
|
<ChevronLeft class="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span class="px-4 text-sm text-muted-foreground">
|
||||||
|
Página {currentPage} de {totalPages}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="rounded-md border bg-card p-2 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
onclick={() => loadPage(currentPage + 1)}
|
||||||
|
disabled={currentPage === totalPages || isLoading}
|
||||||
|
>
|
||||||
|
<ChevronRight class="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
import { obtenerUsuarioPorUsername } from '@/hooks/obtenerUsuario';
|
import { obtenerUsuarioPorUsername } from '@/hooks/obtenerUsuario';
|
||||||
import { error } from '@sveltejs/kit';
|
import { error } from '@sveltejs/kit';
|
||||||
|
import type { PageLoad } from './$types';
|
||||||
import type { UserResponseDto, UsersResponseDto } from '../../../types';
|
import type { UserResponseDto, UsersResponseDto } from '../../../types';
|
||||||
import { obtenerSeguidosPorUsuario } from '@/hooks/obtenerSeguidosPorUsuario';
|
import { obtenerSeguidosPorUsuario } from '@/hooks/obtenerSeguidosPorUsuario';
|
||||||
|
|
||||||
export async function load({ params, fetch }) {
|
export const load: PageLoad = async ({ params, fetch }) => {
|
||||||
const usuario: UserResponseDto | null = await obtenerUsuarioPorUsername(params.perfil, fetch);
|
const usuario: UserResponseDto | null = await obtenerUsuarioPorUsername(params.perfil, fetch);
|
||||||
if (!usuario) error(404, 'No se encontro el usuario, ' + params.perfil);
|
if (!usuario) error(404, 'No se encontro el usuario, ' + params.perfil);
|
||||||
|
|
||||||
const seguidosResponse: UsersResponseDto | null = await obtenerSeguidosPorUsuario(
|
const seguidosResponse: UsersResponseDto | null = await obtenerSeguidosPorUsuario(
|
||||||
usuario.id,
|
usuario.id,
|
||||||
|
1,
|
||||||
100,
|
100,
|
||||||
fetch
|
fetch
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
usuario,
|
usuario,
|
||||||
seguidos: seguidosResponse?.response || []
|
seguidos: seguidosResponse?.response || [],
|
||||||
|
totalCount: seguidosResponse?.totalCount ?? 0
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|||||||
66
src/routes/about/+page.svelte
Normal file
66
src/routes/about/+page.svelte
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<script>
|
||||||
|
import AvatarFallback from '@/components/ui/avatar/avatar-fallback.svelte';
|
||||||
|
import AvatarImage from '@/components/ui/avatar/avatar-image.svelte';
|
||||||
|
import Avatar from '@/components/ui/avatar/avatar.svelte';
|
||||||
|
import CardContent from '@/components/ui/card/card-content.svelte';
|
||||||
|
import CardDescription from '@/components/ui/card/card-description.svelte';
|
||||||
|
import CardHeader from '@/components/ui/card/card-header.svelte';
|
||||||
|
import CardTitle from '@/components/ui/card/card-title.svelte';
|
||||||
|
import Card from '@/components/ui/card/card.svelte';
|
||||||
|
import Separator from '@/components/ui/separator/separator.svelte';
|
||||||
|
import Github from '@lucide/svelte/icons/github';
|
||||||
|
|
||||||
|
let devs = [
|
||||||
|
{
|
||||||
|
name: 'Fede',
|
||||||
|
role: 'Desarrollador Full Stack',
|
||||||
|
pic: 'https://avatars.githubusercontent.com/u/61921832?s=64&v=4',
|
||||||
|
gh: 'https://github.com/fedpo2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Luca',
|
||||||
|
role: 'Desarrollador Frontend',
|
||||||
|
pic: 'https://avatars.githubusercontent.com/u/141507149?s=64&v=4',
|
||||||
|
gh: 'https://github.com/TroianoLuca2am'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Fran',
|
||||||
|
role: 'Desarrollador Backend',
|
||||||
|
pic: 'https://avatars.githubusercontent.com/u/126514899?s=64&v=4',
|
||||||
|
gh: 'https://github.com/franciscorosecerna'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex min-h-fit w-full items-center justify-center p-6 md:p-10">
|
||||||
|
<div class="w-full max-w-6xl">
|
||||||
|
<h1 class="mb-2 scroll-m-20 text-center text-4xl font-extrabold tracking-tight lg:text-5xl">
|
||||||
|
Sobre Nosotros
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<Separator class="my-2" />
|
||||||
|
<div class="grid grid-cols-1 gap-6 md:grid-cols-3">
|
||||||
|
{#each devs as dev}
|
||||||
|
<Card>
|
||||||
|
<CardHeader class="flex flex-col items-center justify-center gap-2">
|
||||||
|
<Avatar class="h-24 w-24">
|
||||||
|
<AvatarImage src={dev.pic} class="h-24 w-24" />
|
||||||
|
<AvatarFallback class="flex h-24 w-24 items-center justify-center text-3xl">
|
||||||
|
{dev.name.toLocaleUpperCase()[0]}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div>
|
||||||
|
<CardTitle>{dev.name}</CardTitle>
|
||||||
|
<CardDescription>{dev.role}</CardDescription>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<a href={dev.gh}><Github /></a>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -10,6 +10,26 @@
|
|||||||
import { updatePost } from '@/hooks/updatePost';
|
import { updatePost } from '@/hooks/updatePost';
|
||||||
import Separator from '@/components/ui/separator/separator.svelte';
|
import Separator from '@/components/ui/separator/separator.svelte';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
|
import { obtenerCantidadDeUsosdeHtag } from '@/hooks/obtenerCantidadDeUsosdeHtag';
|
||||||
|
|
||||||
|
let currentPage = $state(1);
|
||||||
|
let loading = $state(false);
|
||||||
|
|
||||||
|
async function cargarPagina(pageNumber: number) {
|
||||||
|
loading = true;
|
||||||
|
const res = await obtenerCantidadDeUsosdeHtag(
|
||||||
|
data.htag,
|
||||||
|
fetch,
|
||||||
|
pageNumber,
|
||||||
|
20
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
setPosts(res.response);
|
||||||
|
currentPage = pageNumber;
|
||||||
|
}
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
interface props {
|
interface props {
|
||||||
data: {
|
data: {
|
||||||
@@ -78,6 +98,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mt-6 mb-12 flex items-center justify-center gap-4">
|
||||||
|
<button
|
||||||
|
class="rounded-md border bg-card p-2 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
onclick={() => cargarPagina(currentPage - 1)}
|
||||||
|
disabled={currentPage === 1 || loading}
|
||||||
|
>
|
||||||
|
Anterior
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span class="text-sm font-semibold">
|
||||||
|
Página {currentPage}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="rounded-md border bg-card p-2 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
onclick={() => cargarPagina(currentPage + 1)}
|
||||||
|
disabled={loading || (postsfiltro?.length ?? 0) < 20}
|
||||||
|
>
|
||||||
|
Siguiente
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
{#if postAModificar}
|
{#if postAModificar}
|
||||||
<div in:fade>
|
<div in:fade>
|
||||||
<ModalEditar callbackfn={handleEditar} bind:post={postAModificar} />
|
<ModalEditar callbackfn={handleEditar} bind:post={postAModificar} />
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
import { sesionStore } from '@/stores/usuario';
|
import { sesionStore } from '@/stores/usuario';
|
||||||
import { likePost } from '@/hooks/likePost';
|
import { likePost } from '@/hooks/likePost';
|
||||||
import ThumbsUp from '@lucide/svelte/icons/thumbs-up';
|
import ThumbsUp from '@lucide/svelte/icons/thumbs-up';
|
||||||
import { TamañoPantalla } from './TamañoPantalla.svelte';
|
|
||||||
import BotonSeguir from '@/components/BotonSeguir.svelte';
|
import BotonSeguir from '@/components/BotonSeguir.svelte';
|
||||||
import Pen from '@lucide/svelte/icons/pen';
|
import Pen from '@lucide/svelte/icons/pen';
|
||||||
import Trash_2 from '@lucide/svelte/icons/trash-2';
|
import Trash_2 from '@lucide/svelte/icons/trash-2';
|
||||||
@@ -25,6 +24,8 @@
|
|||||||
import TooltipContent from '@/components/ui/tooltip/tooltip-content.svelte';
|
import TooltipContent from '@/components/ui/tooltip/tooltip-content.svelte';
|
||||||
import { deletePost } from '@/hooks/deletePost';
|
import { deletePost } from '@/hooks/deletePost';
|
||||||
import { flip } from 'svelte/animate';
|
import { flip } from 'svelte/animate';
|
||||||
|
import { obtenerRespuestasPorId } from '@/hooks/obtenerRespuestasPorId';
|
||||||
|
import VolverArriba from '@/components/VolverArriba.svelte';
|
||||||
|
|
||||||
interface Prop {
|
interface Prop {
|
||||||
data: {
|
data: {
|
||||||
@@ -33,10 +34,18 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let tamaño = new TamañoPantalla();
|
|
||||||
|
|
||||||
let { data }: Prop = $props();
|
let { data }: Prop = $props();
|
||||||
// $inspect(data);
|
|
||||||
|
let respuestasPaginadas: Post[] = $state([]);
|
||||||
|
let pagerespuestas: number = $state(1);
|
||||||
|
|
||||||
|
let seguirMostrandoMostrarMás = $derived.by(() => {
|
||||||
|
if (data.post.repliesCount <= 20) return false;
|
||||||
|
if (respuestasPaginadas.length == 0) return true;
|
||||||
|
return data.respuestas.length + respuestasPaginadas.length < data.post.repliesCount;
|
||||||
|
});
|
||||||
|
// $inspect([respuestasPaginadas, seguirMostrandoMostrarMás]);
|
||||||
|
|
||||||
let postAModificar: Post | null = $state(null);
|
let postAModificar: Post | null = $state(null);
|
||||||
|
|
||||||
async function handleEditar(e: SubmitEvent) {
|
async function handleEditar(e: SubmitEvent) {
|
||||||
@@ -83,7 +92,7 @@
|
|||||||
<div class="flex min-h-fit w-full items-center justify-center p-6 md:p-10">
|
<div class="flex min-h-fit w-full items-center justify-center p-6 md:p-10">
|
||||||
<div class="w-full max-w-6xl">
|
<div class="w-full max-w-6xl">
|
||||||
{#if data.post}
|
{#if data.post}
|
||||||
<div class="sticky top-0 z-10 w-full rounded-xl bg-background p-2">
|
<div class="top-1 z-10 w-full rounded-xl bg-background p-2">
|
||||||
<PostCard post={data.post} bind:postAModificar update={() => invalidate('post:post')} />
|
<PostCard post={data.post} bind:postAModificar update={() => invalidate('post:post')} />
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -94,17 +103,19 @@
|
|||||||
</Content>
|
</Content>
|
||||||
</Card>
|
</Card>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="my-4">
|
{#if $sesionStore}
|
||||||
<Separator></Separator>
|
<div class="my-4">
|
||||||
</div>
|
<Separator></Separator>
|
||||||
<CrearPost placeholder={`Responder a @${data.post.authorName}`} parentPostId={data.post.id} />
|
</div>
|
||||||
|
<CrearPost placeholder={`Responder a @${data.post.authorName}`} parentPostId={data.post.id} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="my-4">
|
<div class="my-4">
|
||||||
<Separator></Separator>
|
<Separator></Separator>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
{#each data.respuestas as respuesta (respuesta.id)}
|
{#each [...data.respuestas, ...respuestasPaginadas] as respuesta (respuesta.id)}
|
||||||
<div transition:fade animate:flip>
|
<div transition:fade animate:flip>
|
||||||
<!-- {#if tamaño.isMobile} -->
|
<!-- {#if tamaño.isMobile} -->
|
||||||
<!-- {#if true} -->
|
<!-- {#if true} -->
|
||||||
@@ -114,6 +125,26 @@
|
|||||||
<!-- {/if} -->
|
<!-- {/if} -->
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
{#if seguirMostrandoMostrarMás}
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
onclick={async () => {
|
||||||
|
let ret = await obtenerRespuestasPorId(
|
||||||
|
data.post.id,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
++pagerespuestas
|
||||||
|
);
|
||||||
|
if (ret == null) return;
|
||||||
|
if (typeof ret == 'string') return;
|
||||||
|
if (ret.length == 0) {
|
||||||
|
seguirMostrandoMostrarMás = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
respuestasPaginadas.push(...ret);
|
||||||
|
}}>Cargar Más Respuestas</Button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -123,6 +154,8 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<VolverArriba />
|
||||||
|
|
||||||
{#snippet Respuesta(post: Post)}
|
{#snippet Respuesta(post: Post)}
|
||||||
<div class="ml-2 flex-1">
|
<div class="ml-2 flex-1">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
@@ -180,8 +213,8 @@
|
|||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>Borrar</TooltipContent>
|
<TooltipContent>Borrar</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
<BotonSeguir {post} variant="icon-sm" />
|
||||||
{/if}
|
{/if}
|
||||||
<BotonSeguir {post} variant="icon-sm" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class=" mt-1 line-clamp-2 rounded-md p-2 text-lg">
|
<p class=" mt-1 line-clamp-2 rounded-md p-2 text-lg">
|
||||||
|
|||||||
@@ -6,8 +6,12 @@
|
|||||||
import FireBaseButton from '@/components/FireBaseButton.svelte';
|
import FireBaseButton from '@/components/FireBaseButton.svelte';
|
||||||
import Card from '@/components/ui/card/card.svelte';
|
import Card from '@/components/ui/card/card.svelte';
|
||||||
import CardContent from '@/components/ui/card/card-content.svelte';
|
import CardContent from '@/components/ui/card/card-content.svelte';
|
||||||
|
import Dialog from '@/components/ui/dialog/dialog.svelte';
|
||||||
|
import DialogContent from '@/components/ui/dialog/dialog-content.svelte';
|
||||||
|
import Spinner from '@/components/ui/spinner/spinner.svelte';
|
||||||
|
|
||||||
let showAlert: boolean = $state(false);
|
let showAlert: boolean = $state(false);
|
||||||
|
let openload = $state(false);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
resetAlert();
|
resetAlert();
|
||||||
@@ -26,7 +30,7 @@
|
|||||||
<SignupForm bind:showAlert />
|
<SignupForm bind:showAlert />
|
||||||
|
|
||||||
<Card class="mt-2">
|
<Card class="mt-2">
|
||||||
<CardContent>
|
<CardContent onclick={() => (openload = true)}>
|
||||||
<FireBaseButton mode="register" />
|
<FireBaseButton mode="register" />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -42,6 +46,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if openload}
|
||||||
|
<Card class="fixed inset-0 z-50 flex items-center justify-center bg-black/80">
|
||||||
|
<CardContent class="flex w-fit items-center justify-center p-8">
|
||||||
|
<Spinner class="size-11" />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<meta property="og:title" content="Mini-x" />
|
<meta property="og:title" content="Mini-x" />
|
||||||
<meta property="og:description" content={`Registra un usuario`} />
|
<meta property="og:description" content={`Registra un usuario`} />
|
||||||
|
|||||||
@@ -13,31 +13,34 @@
|
|||||||
|
|
||||||
<div class="flex min-h-fit w-full flex-col items-center justify-center gap-2 p-6 md:p-10">
|
<div class="flex min-h-fit w-full flex-col items-center justify-center gap-2 p-6 md:p-10">
|
||||||
<div class="flex w-full max-w-6xl flex-col gap-2">
|
<div class="flex w-full max-w-6xl flex-col gap-2">
|
||||||
<h1 class="text-2xl font-bold">Usuarios</h1>
|
{#if data.usuarios.length != 0}
|
||||||
<Separator></Separator>
|
<h1 class="text-2xl font-bold">Usuarios</h1>
|
||||||
{#each data.usuarios as usu}
|
<Separator></Separator>
|
||||||
<div class="w-full">
|
{#each data.usuarios as usu}
|
||||||
<UserCard {usu} />
|
<div class="w-full">
|
||||||
</div>
|
<UserCard {usu} />
|
||||||
{/each}
|
</div>
|
||||||
|
|
||||||
<div class="mt-4">
|
|
||||||
<h2 class="mb-2 text-xl font-semibold">Hastags</h2>
|
|
||||||
</div>
|
|
||||||
<Separator />
|
|
||||||
<div class="mt-4 flex flex-col gap-2">
|
|
||||||
{#each data.htags as htag}
|
|
||||||
<a
|
|
||||||
href={`/htag/${htag}`}
|
|
||||||
class="w-full rounded-lg bg-accent p-3 text-lg font-medium text-foreground hover:bg-muted"
|
|
||||||
>
|
|
||||||
<div class="flex justify-between">
|
|
||||||
#{htag}
|
|
||||||
<ChevronRight />
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
{/if}
|
||||||
|
{#if data.htags.length != 0}
|
||||||
|
<div class="mt-4">
|
||||||
|
<h2 class="mb-2 text-xl font-semibold">Hastags</h2>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<div class="mt-4 flex flex-col gap-2">
|
||||||
|
{#each data.htags as htag}
|
||||||
|
<a
|
||||||
|
href={`/htag/${htag}`}
|
||||||
|
class="w-full rounded-lg bg-accent p-3 text-lg font-medium text-foreground hover:bg-muted"
|
||||||
|
>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
#{htag}
|
||||||
|
<ChevronRight />
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ export async function load({ params }) {
|
|||||||
return error(500, 'No se pudo alcanzar el servidor.');
|
return error(500, 'No se pudo alcanzar el servidor.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (usuarios.length == 0) {
|
if (usuarios.length == 0 && htags.length == 0) {
|
||||||
return error(404, 'No se encontraron usuarios que coinsidan con la busqueda.');
|
return error(404, 'No se encontraron usuarios ni hashtags que coinsidan con la busqueda.');
|
||||||
}
|
}
|
||||||
return { usuarios, htags };
|
return { usuarios, htags };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user