mirror of
https://github.com/emailerfacu-spec/minix-front.git
synced 2026-04-19 16:07:32 -03:00
Compare commits
19 Commits
398592ea1d
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b4800d0d9 | |||
| 022623e22c | |||
| c96df5af92 | |||
|
|
d278c75688 | ||
| 3e07252c6f | |||
| ec9ec1f58a | |||
|
|
4ca434da9e | ||
|
|
1c4140e0a1 | ||
|
|
cdc9e74095 | ||
| 8168ceb00d | |||
|
|
1070b71f1f | ||
|
|
70093ce186 | ||
| 1da53348d0 | |||
| 9546b6548d | |||
| 52c583a039 | |||
| 7b91483514 | |||
| 85477ffe7e | |||
| 826d6478d7 | |||
| 5b5f82c0d6 |
16
bun.lock
16
bun.lock
@@ -7,7 +7,7 @@
|
||||
"dependencies": {
|
||||
"@firebase/auth": "^1.12.0",
|
||||
"@resvg/resvg-js": "^2.6.2",
|
||||
"firebase": "^12.8.0",
|
||||
"firebase": "^12.9.0",
|
||||
"mode-watcher": "^1.1.0",
|
||||
"satori": "^0.18.3",
|
||||
},
|
||||
@@ -87,7 +87,7 @@
|
||||
|
||||
"@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=="],
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
|
||||
"@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=="],
|
||||
|
||||
@@ -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/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=="],
|
||||
|
||||
@@ -451,7 +451,7 @@
|
||||
|
||||
"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=="],
|
||||
|
||||
@@ -681,6 +681,8 @@
|
||||
|
||||
"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=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"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/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": {
|
||||
"@firebase/auth": "^1.12.0",
|
||||
"@resvg/resvg-js": "^2.6.2",
|
||||
"firebase": "^12.8.0",
|
||||
"firebase": "^12.9.0",
|
||||
"mode-watcher": "^1.1.0",
|
||||
"satori": "^0.18.3"
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
import { seguirUsuario } from '@/hooks/seguirUsuario';
|
||||
import type { Post } from '../../types';
|
||||
import CardError from './CardError.svelte';
|
||||
import { cacheSeguidos } from '@/stores/cacheSeguidos.svelte';
|
||||
import { cacheSeguidos } from '@/stores/cacheSeguidos.js';
|
||||
|
||||
let {
|
||||
post,
|
||||
@@ -41,16 +41,13 @@
|
||||
});
|
||||
|
||||
async function cargarSeguido() {
|
||||
let a = cacheSeguidos.get(post.authorId);
|
||||
if (a === undefined) {
|
||||
const seguidoStatus = await esSeguido(post as Post);
|
||||
if (seguidoStatus) {
|
||||
cacheSeguidos.set(post.authorId, seguidoStatus.isFollowing || false);
|
||||
seguido = seguidoStatus.isFollowing || false;
|
||||
seguido = await cacheSeguidos.getOrFetch(
|
||||
post.authorId,
|
||||
async () => {
|
||||
const seguidoStatus = await esSeguido(post as Post);
|
||||
return seguidoStatus?.isFollowing || false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
seguido = a;
|
||||
);
|
||||
}
|
||||
|
||||
let mensajeError: string | null = $state(null);
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
let usu = $state({ displayName: data.displayName, bio: data.bio });
|
||||
|
||||
let contenido = $derived(() => {
|
||||
if (data.bio == '') return '';
|
||||
let t = data.bio
|
||||
.replaceAll('&', '')
|
||||
.replaceAll('<', '')
|
||||
@@ -134,7 +135,7 @@
|
||||
</h1>
|
||||
{#if data.bio}
|
||||
<p class="mt-4 rounded-4xl bg-accent p-4 text-center text-muted-foreground">
|
||||
{@html usu.bio.replaceAll('\n', '<br>')}
|
||||
{@html contenido()}
|
||||
</p>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
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 TooltipContent from './ui/tooltip/tooltip-content.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 { 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 BorrarUsuario from './BorrarUsuario.svelte';
|
||||
import InputGroup from './ui/input-group/input-group.svelte';
|
||||
@@ -30,12 +25,25 @@
|
||||
import AgregarUsuario from './admin/AgregarUsuario.svelte';
|
||||
import DarAdmin from './admin/DarAdmin.svelte';
|
||||
import { busquedaAdminUsuarios } from '@/hooks/busquedaAdminUsuarios';
|
||||
import { invalidate, replaceState } from '$app/navigation';
|
||||
|
||||
interface Props {
|
||||
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 openModificarUsuario = $state(false);
|
||||
@@ -48,13 +56,11 @@
|
||||
let usuarioModificar: UserResponseDto | null = $state(null);
|
||||
let usuarioDarAdmin: UserResponseDto | null = $state(null);
|
||||
|
||||
let search = $state('');
|
||||
|
||||
type SortKey = 'username' | 'displayName' | 'postsCount' | 'createdAt';
|
||||
let sortBy = $state<SortKey | null>(null);
|
||||
let sortDirection = $state<'asc' | 'desc'>('asc');
|
||||
|
||||
let usuariosFiltrados = $state(usuarios);
|
||||
let usuariosFiltrados = $derived(usuarios);
|
||||
|
||||
function ordenarPor(campo: SortKey) {
|
||||
if (sortBy === campo) {
|
||||
@@ -82,6 +88,7 @@
|
||||
const sb = b[key].toString().toLowerCase();
|
||||
return sortDirection === 'asc' ? sa.localeCompare(sb) : sb.localeCompare(sa);
|
||||
});
|
||||
paginaActual = 1;
|
||||
}
|
||||
|
||||
function getSortIcon(campo: SortKey) {
|
||||
@@ -89,46 +96,37 @@
|
||||
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);
|
||||
let timeoutId: ReturnType<typeof setTimeout> | number | undefined;
|
||||
|
||||
function buscarUsuarios() {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
timeoutId = setTimeout(async () => {
|
||||
if (search === '') {
|
||||
usuariosFiltrados = usuarios;
|
||||
return;
|
||||
const url = new URL(window.location.href);
|
||||
if (!search.trim()) {
|
||||
url.searchParams.delete('q');
|
||||
} else {
|
||||
url.searchParams.set('q', search);
|
||||
}
|
||||
usuariosFiltrados = await busquedaAdminUsuarios(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>
|
||||
|
||||
<div class="mb-4 flex gap-2">
|
||||
@@ -186,7 +184,11 @@
|
||||
<TableCell class="flex gap-2">
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Button onclick={() => handleCambiarContraseña(usuario)}><KeyIcon></KeyIcon></Button
|
||||
<Button
|
||||
onclick={() => {
|
||||
open = true;
|
||||
usuarioCambioPass = usuario;
|
||||
}}><KeyIcon></KeyIcon></Button
|
||||
>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
@@ -195,7 +197,12 @@
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Button onclick={() => handleModificar(usuario)}><UserPen /></Button>
|
||||
<Button
|
||||
onclick={() => {
|
||||
openModificarUsuario = true;
|
||||
usuarioModificar = usuario;
|
||||
}}><UserPen /></Button
|
||||
>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Modificar Usuario</p>
|
||||
@@ -205,7 +212,10 @@
|
||||
<TooltipTrigger>
|
||||
<Button
|
||||
disabled={usuario.isAdmin}
|
||||
onclick={() => handleBorrar(usuario)}
|
||||
onclick={() => {
|
||||
openBorrar = true;
|
||||
usuarioBorrar = usuario;
|
||||
}}
|
||||
variant="destructive"><Trash_2 /></Button
|
||||
>
|
||||
</TooltipTrigger>
|
||||
@@ -221,7 +231,10 @@
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Button
|
||||
onclick={() => handleDarAdmin(usuario)}
|
||||
onclick={() => {
|
||||
openDarAdmin = true;
|
||||
usuarioDarAdmin = usuario;
|
||||
}}
|
||||
variant={usuario.isAdmin ? 'destructive' : 'default'}
|
||||
>
|
||||
<Shield />
|
||||
@@ -241,6 +254,31 @@
|
||||
{/if}
|
||||
</TableBody>
|
||||
</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} />
|
||||
<RecuperarContraseña bind:open usuario={usuarioCambioPass} />
|
||||
<ModificarUsuario bind:open={openModificarUsuario} bind:usuario={usuarioModificar} />
|
||||
|
||||
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>
|
||||
<div
|
||||
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>
|
||||
{mensajeResultado}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
{/if}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import Label from '../ui/label/label.svelte';
|
||||
import Spinner from '../ui/spinner/spinner.svelte';
|
||||
import { updateUsuario } from '@/hooks/updateUsuario';
|
||||
import { invalidate } from '$app/navigation';
|
||||
|
||||
interface Prop {
|
||||
open: boolean;
|
||||
@@ -38,6 +39,7 @@
|
||||
error = ret;
|
||||
} else {
|
||||
usuario!.displayName = ret.displayName;
|
||||
invalidate('admin:load');
|
||||
open = false;
|
||||
}
|
||||
cargando = false;
|
||||
|
||||
@@ -50,13 +50,13 @@
|
||||
<header class="border-b bg-background/95 backdrop-blur">
|
||||
<div class="mx-4 ms-2 flex h-12 items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<a data-sveltekit-preload-data={false} 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
|
||||
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" />
|
||||
</Avatar>
|
||||
</a>
|
||||
</button>
|
||||
<!-- <nav class="me-2 items-center space-x-6 text-sm font-medium md:flex">
|
||||
<ButtonTheme />
|
||||
</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 };
|
||||
}
|
||||
@@ -2,15 +2,20 @@ import { apiBase } from '@/stores/url';
|
||||
import { sesionStore } from '@/stores/usuario';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
export async function busquedaAdminUsuarios(q: string) {
|
||||
export async function busquedaAdminUsuarios(q: string, limit = 5, page = 1, fetch2?: Function) {
|
||||
try {
|
||||
const response = await fetch(get(apiBase) + '/api/admin/users?q=' + q, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { getPosts } from './getPosts';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { cacheSeguidos } from '@/stores/cacheSeguidos.svelte';
|
||||
import { cacheSeguidos } from '@/stores/cacheSeguidos';
|
||||
import { apiBase } from '@/stores/url';
|
||||
import { sesionStore } from '@/stores/usuario';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
@@ -2,14 +2,18 @@ import { apiBase } from '@/stores/url';
|
||||
import { sesionStore } from '@/stores/usuario';
|
||||
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;
|
||||
const fetchFn = fetch2 || fetch;
|
||||
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',
|
||||
headers: {
|
||||
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
||||
Authorization: `Bearer ${get(sesionStore)?.accessToken || ''}`
|
||||
}
|
||||
});
|
||||
if (req.ok) {
|
||||
|
||||
@@ -16,7 +16,7 @@ export async function obtenerPostPorId(
|
||||
const req = await fetchFn(`${get(apiBase)}/api/posts/${idpost}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
||||
Authorization: `Bearer ${get(sesionStore)?.accessToken || ''}`
|
||||
}
|
||||
});
|
||||
let data = await req.json();
|
||||
|
||||
@@ -15,7 +15,7 @@ export async function obtenerRespuestasPorId(
|
||||
const req = await fetchFn(`${get(apiBase)}/api/posts/${id}/replies?page=${page}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
||||
Authorization: `Bearer ${get(sesionStore)?.accessToken || ''}`
|
||||
}
|
||||
});
|
||||
if (req.ok) {
|
||||
|
||||
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();
|
||||
@@ -34,4 +34,4 @@ export function getFirebaseAuth(): Auth {
|
||||
auth = getAuth(getFirebaseApp());
|
||||
}
|
||||
return auth;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import type { Post } from '../../types';
|
||||
|
||||
export const posts = writable<Post[]>([]);
|
||||
export const posts = writable<Post[] | null>(null);
|
||||
export const loadingPosts = writable(false);
|
||||
export const page = writable(1);
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ if (browser) {
|
||||
const user = auth.currentUser;
|
||||
|
||||
if (user) {
|
||||
const token = await user.getIdToken(true);
|
||||
const token = await user.getIdToken();
|
||||
currentSesion.update((s) => {
|
||||
if (s) {
|
||||
return { ...s, accessToken: token };
|
||||
@@ -72,6 +72,7 @@ if (browser) {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error obteniendo token de Firebase:', error);
|
||||
sesionStore.reset();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -81,8 +82,14 @@ if (browser) {
|
||||
|
||||
const expirationTime = decoded.exp * 1000;
|
||||
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
|
||||
};
|
||||
|
||||
@@ -117,7 +124,7 @@ if (browser) {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error refrescando token:', error);
|
||||
currentSesion.set(null);
|
||||
sesionStore.reset();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import CardContent from '@/components/ui/card/card-content.svelte';
|
||||
import Card from '@/components/ui/card/card.svelte';
|
||||
import CardDescription from '@/components/ui/card/card-description.svelte';
|
||||
import TablaUsuarios from '@/components/TablaUsuarios.svelte';
|
||||
import CardTitle from '@/components/ui/card/card-title.svelte';
|
||||
import CardHeader from '@/components/ui/card/card-header.svelte';
|
||||
@@ -9,7 +8,8 @@
|
||||
|
||||
interface Prop {
|
||||
data: {
|
||||
usuarios?: UserResponseDto[];
|
||||
usuarios: UserResponseDto[];
|
||||
hayMas: boolean;
|
||||
error: boolean;
|
||||
};
|
||||
}
|
||||
@@ -28,11 +28,7 @@
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{#if data.usuarios?.length === 0}
|
||||
<CardDescription>No hay usuarios que mostar</CardDescription>
|
||||
{:else}
|
||||
<TablaUsuarios usuarios={data.usuarios || []}></TablaUsuarios>
|
||||
{/if}
|
||||
<TablaUsuarios usuarios={data.usuarios} hayMas={data.hayMas}></TablaUsuarios>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
import { apiBase } from '@/stores/url.js';
|
||||
import { sesionStore } from '@/stores/usuario';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { get } from 'svelte/store';
|
||||
import type { UserResponseDto } from '../../../types.js';
|
||||
import { busquedaAdminUsuarios } from '@/hooks/busquedaAdminUsuarios.js';
|
||||
import type { PageLoad } from './$types.js';
|
||||
import { fetchUsuariosAdmin } from '@/hooks/UsuariosAdmin.js';
|
||||
|
||||
export const ssr = false;
|
||||
|
||||
export async function load({ depends, fetch }) {
|
||||
export const load: PageLoad = async ({ depends, fetch }) => {
|
||||
depends('admin:load');
|
||||
const response = await fetch(get(apiBase) + '/api/admin/users', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
||||
}
|
||||
});
|
||||
if (response.status === 401) {
|
||||
throw redirect(302, '/');
|
||||
let url = new URL(location.href);
|
||||
let query = url.searchParams.get('q') ?? '';
|
||||
let page = Number(url.searchParams.get('p'));
|
||||
if (isNaN(page) || page < 1) {
|
||||
page = 1;
|
||||
}
|
||||
if (!response.ok) {
|
||||
|
||||
const result = await busquedaAdminUsuarios(query, 5, page, fetch);
|
||||
|
||||
if (result.error) {
|
||||
return { error: true };
|
||||
}
|
||||
|
||||
const usuarios: UserResponseDto[] = await response.json();
|
||||
|
||||
return { usuarios, error: false };
|
||||
}
|
||||
return {
|
||||
usuarios: result.usuarios,
|
||||
hayMas: result.hayMas,
|
||||
error: false
|
||||
};
|
||||
};
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
let mensajeError = $state('');
|
||||
let sentinel: HTMLDivElement;
|
||||
|
||||
// $inspect($posts);
|
||||
|
||||
resetPosts();
|
||||
$effect(() => {
|
||||
loadMorePosts();
|
||||
@@ -78,19 +80,13 @@
|
||||
{/if}
|
||||
<hr />
|
||||
|
||||
{#if $posts.length === 0 && $loadingPosts}
|
||||
{#if $posts === null || $posts.length === 0}
|
||||
<Card>
|
||||
<Content class="flex items-center justify-center gap-2">
|
||||
<Spinner class="h-10 w-10" />
|
||||
<p>Cargando</p>
|
||||
</Content>
|
||||
</Card>
|
||||
{:else if $posts.length === 0}
|
||||
<Card>
|
||||
<Content>
|
||||
<p class="text-center leading-7">No hay Posts que mostrar</p>
|
||||
</Content>
|
||||
</Card>
|
||||
{:else}
|
||||
{#each $posts as post (post.id)}
|
||||
<div transition:slide>
|
||||
@@ -101,7 +97,7 @@
|
||||
</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">
|
||||
<Spinner />
|
||||
</div>
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
`${$apiBase}/api/posts/user/${params.perfil}?page=${pageNumber}&pageSize=20`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${$sesionStore?.accessToken}`
|
||||
Authorization: `Bearer ${$sesionStore?.accessToken || ''}`
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -10,6 +10,26 @@
|
||||
import { updatePost } from '@/hooks/updatePost';
|
||||
import Separator from '@/components/ui/separator/separator.svelte';
|
||||
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 {
|
||||
data: {
|
||||
@@ -78,6 +98,27 @@
|
||||
</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}
|
||||
<div in:fade>
|
||||
<ModalEditar callbackfn={handleEditar} bind:post={postAModificar} />
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
import { sesionStore } from '@/stores/usuario';
|
||||
import { likePost } from '@/hooks/likePost';
|
||||
import ThumbsUp from '@lucide/svelte/icons/thumbs-up';
|
||||
import { TamañoPantalla } from './TamañoPantalla.svelte';
|
||||
import BotonSeguir from '@/components/BotonSeguir.svelte';
|
||||
import Pen from '@lucide/svelte/icons/pen';
|
||||
import Trash_2 from '@lucide/svelte/icons/trash-2';
|
||||
@@ -26,6 +25,7 @@
|
||||
import { deletePost } from '@/hooks/deletePost';
|
||||
import { flip } from 'svelte/animate';
|
||||
import { obtenerRespuestasPorId } from '@/hooks/obtenerRespuestasPorId';
|
||||
import VolverArriba from '@/components/VolverArriba.svelte';
|
||||
|
||||
interface Prop {
|
||||
data: {
|
||||
@@ -34,8 +34,6 @@
|
||||
};
|
||||
}
|
||||
|
||||
let tamaño = new TamañoPantalla();
|
||||
|
||||
let { data }: Prop = $props();
|
||||
|
||||
let respuestasPaginadas: Post[] = $state([]);
|
||||
@@ -94,7 +92,7 @@
|
||||
<div class="flex min-h-fit w-full items-center justify-center p-6 md:p-10">
|
||||
<div class="w-full max-w-6xl">
|
||||
{#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')} />
|
||||
</div>
|
||||
{:else}
|
||||
@@ -105,10 +103,12 @@
|
||||
</Content>
|
||||
</Card>
|
||||
{/if}
|
||||
<div class="my-4">
|
||||
<Separator></Separator>
|
||||
</div>
|
||||
<CrearPost placeholder={`Responder a @${data.post.authorName}`} parentPostId={data.post.id} />
|
||||
{#if $sesionStore}
|
||||
<div class="my-4">
|
||||
<Separator></Separator>
|
||||
</div>
|
||||
<CrearPost placeholder={`Responder a @${data.post.authorName}`} parentPostId={data.post.id} />
|
||||
{/if}
|
||||
|
||||
<div class="my-4">
|
||||
<Separator></Separator>
|
||||
@@ -154,6 +154,8 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<VolverArriba />
|
||||
|
||||
{#snippet Respuesta(post: Post)}
|
||||
<div class="ml-2 flex-1">
|
||||
<div class="flex justify-between">
|
||||
@@ -211,8 +213,8 @@
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Borrar</TooltipContent>
|
||||
</Tooltip>
|
||||
<BotonSeguir {post} variant="icon-sm" />
|
||||
{/if}
|
||||
<BotonSeguir {post} variant="icon-sm" />
|
||||
</div>
|
||||
</div>
|
||||
<p class=" mt-1 line-clamp-2 rounded-md p-2 text-lg">
|
||||
|
||||
@@ -6,8 +6,12 @@
|
||||
import FireBaseButton from '@/components/FireBaseButton.svelte';
|
||||
import Card from '@/components/ui/card/card.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 openload = $state(false);
|
||||
|
||||
$effect(() => {
|
||||
resetAlert();
|
||||
@@ -26,7 +30,7 @@
|
||||
<SignupForm bind:showAlert />
|
||||
|
||||
<Card class="mt-2">
|
||||
<CardContent>
|
||||
<CardContent onclick={() => (openload = true)}>
|
||||
<FireBaseButton mode="register" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -42,6 +46,14 @@
|
||||
</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>
|
||||
<meta property="og:title" content="Mini-x" />
|
||||
<meta property="og:description" content={`Registra un usuario`} />
|
||||
|
||||
Reference in New Issue
Block a user