mirror of
https://github.com/emailerfacu-spec/minix-front.git
synced 2026-04-19 16:07:32 -03:00
Compare commits
47 Commits
firebase-O
...
398592ea1d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
398592ea1d | ||
| dc9c3b4f3c | |||
|
|
93c4b15d5b | ||
| 7901a677d5 | |||
| 0ea873e039 | |||
| c0d637a34c | |||
| e1864660a7 | |||
| 37f1b46306 | |||
| 1f4166a651 | |||
| b5ba2437b4 | |||
| ceec19fb91 | |||
| 9c59c3d036 | |||
| 921836c115 | |||
|
|
54593dd2eb | ||
|
|
9eb92b0c06 | ||
| 1c79c5fcda | |||
|
|
000c65712f | ||
|
|
d35de05a7b | ||
|
|
0ba88d14ed | ||
| 65d65baaee | |||
| e4508a0487 | |||
| 6cf920799c | |||
| ebc2ebc322 | |||
| 8fbe62d391 | |||
|
|
4d99695a59 | ||
|
|
1f2f26f780 | ||
|
|
3dc0cfc8a4 | ||
|
|
22ecdb6e9d | ||
|
|
e785b60935 | ||
|
|
c47b9956b9 | ||
|
|
61232ada05 | ||
|
|
fae9a676e2 | ||
|
|
9b0937b731 | ||
|
|
77f3901cb3 | ||
|
|
bd09ae005b | ||
|
|
d1a26cc132 | ||
|
|
670f8ae3e2 | ||
|
|
b4382b361a | ||
|
|
17f7bed1d9 | ||
|
|
29b7effd57 | ||
|
|
f425dd13a7 | ||
|
|
09ddb0800c | ||
|
|
d60daa624c | ||
|
|
ee5535dbc6 | ||
|
|
8c0da761e6 | ||
|
|
ef16f649dd | ||
|
|
8decf85d3b |
@@ -1,4 +1,5 @@
|
||||
# Package Managers
|
||||
src/lib/components/ui/
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
yarn.lock
|
||||
|
||||
@@ -3,10 +3,7 @@
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": [
|
||||
"prettier-plugin-svelte",
|
||||
"prettier-plugin-tailwindcss"
|
||||
],
|
||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||
"overrides": [
|
||||
{
|
||||
"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,16 +16,18 @@
|
||||
let {
|
||||
post,
|
||||
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') {
|
||||
window.addEventListener('followCacheUpdated', ((
|
||||
event: CustomEvent<{ userId: string; isFollowed: boolean } | { clearAll: true }>
|
||||
) => {
|
||||
if ('clearAll' in event.detail && event.detail.clearAll === true) {
|
||||
cargarSeguido();
|
||||
} else if ('userId' in event.detail && event.detail.userId === post.authorId) {
|
||||
seguido = event.detail.isFollowed;
|
||||
}
|
||||
@@ -41,9 +43,11 @@
|
||||
async function cargarSeguido() {
|
||||
let a = cacheSeguidos.get(post.authorId);
|
||||
if (a === undefined) {
|
||||
const seguidoStatus = await esSeguido(post);
|
||||
cacheSeguidos.set(post.authorId, seguidoStatus.isFollowing || false);
|
||||
seguido = seguidoStatus.isFollowing || false;
|
||||
const seguidoStatus = await esSeguido(post as Post);
|
||||
if (seguidoStatus) {
|
||||
cacheSeguidos.set(post.authorId, seguidoStatus.isFollowing || false);
|
||||
seguido = seguidoStatus.isFollowing || false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
seguido = a;
|
||||
|
||||
@@ -104,10 +104,10 @@
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</h1>
|
||||
{#if usu.bio}
|
||||
{#if data.bio}
|
||||
<p class="mt-4 rounded-4xl bg-accent p-4 text-center text-muted-foreground">
|
||||
{@html contenido()}
|
||||
<!-- {usu.bio.replaceAll('<', '')} -->
|
||||
@@ -129,10 +129,10 @@
|
||||
</Avatar>
|
||||
</div>
|
||||
<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>
|
||||
</h1>
|
||||
{#if usu.bio}
|
||||
{#if data.bio}
|
||||
<p class="mt-4 rounded-4xl bg-accent p-4 text-center text-muted-foreground">
|
||||
{@html usu.bio.replaceAll('\n', '<br>')}
|
||||
</p>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
import { updateUsuario } from '@/hooks/updateUsuario';
|
||||
import DialogFooter from './ui/dialog/dialog-footer.svelte';
|
||||
import Spinner from './ui/spinner/spinner.svelte';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { invalidate, invalidateAll } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
|
||||
let { data = $bindable(), children } = $props();
|
||||
@@ -42,9 +42,9 @@
|
||||
});
|
||||
cargando = false;
|
||||
open = false;
|
||||
// invalidateAll();
|
||||
await invalidate(page.url);
|
||||
await invalidate('perfil:general');
|
||||
await invalidateAll();
|
||||
// await invalidate(page.url);
|
||||
// await invalidate('perfil:general');
|
||||
}
|
||||
|
||||
function onkeydown(e: KeyboardEvent) {
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
}
|
||||
cargando = true;
|
||||
try {
|
||||
await cambiarContraseñaUsuario(data.id, passwordData.oldPassword, passwordData.newPassword);
|
||||
await cambiarContraseñaUsuario(passwordData.oldPassword, passwordData.newPassword, data.id);
|
||||
cargando = false;
|
||||
open = false;
|
||||
passwordData.oldPassword = '';
|
||||
@@ -103,7 +103,7 @@
|
||||
/>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<Button type="submit" disabled={!coinsiden || cargando}>
|
||||
<Button type="submit" class="mt-6" disabled={!coinsiden || cargando}>
|
||||
{#if cargando}
|
||||
<Spinner />
|
||||
{:else}
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
import InputGroupInput from './ui/input-group/input-group-input.svelte';
|
||||
import AgregarUsuario from './admin/AgregarUsuario.svelte';
|
||||
import DarAdmin from './admin/DarAdmin.svelte';
|
||||
import { busquedaAdminUsuarios } from '@/hooks/busquedaAdminUsuarios';
|
||||
|
||||
interface Props {
|
||||
usuarios: UserResponseDto[];
|
||||
@@ -43,12 +44,8 @@
|
||||
let opencrearUsuario = $state(false);
|
||||
|
||||
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 usuarioModificar: UserResponseDto | null = $state(null);
|
||||
|
||||
let usuarioDarAdmin: UserResponseDto | null = $state(null);
|
||||
|
||||
let search = $state('');
|
||||
@@ -57,6 +54,8 @@
|
||||
let sortBy = $state<SortKey | null>(null);
|
||||
let sortDirection = $state<'asc' | 'desc'>('asc');
|
||||
|
||||
let usuariosFiltrados = $state(usuarios);
|
||||
|
||||
function ordenarPor(campo: SortKey) {
|
||||
if (sortBy === campo) {
|
||||
sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
|
||||
@@ -64,38 +63,27 @@
|
||||
sortBy = campo;
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
if (sortBy !== campo) return '';
|
||||
return sortDirection === 'asc' ? '↑' : '↓';
|
||||
@@ -122,12 +110,36 @@
|
||||
}
|
||||
|
||||
// $inspect(usuarios);
|
||||
let timeoutId: ReturnType<typeof setTimeout> | number | undefined;
|
||||
|
||||
function buscarUsuarios() {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
timeoutId = setTimeout(async () => {
|
||||
if (search === '') {
|
||||
usuariosFiltrados = usuarios;
|
||||
return;
|
||||
}
|
||||
usuariosFiltrados = await busquedaAdminUsuarios(search);
|
||||
}, 200);
|
||||
|
||||
return () => {
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="mb-4 flex gap-2">
|
||||
<InputGroup>
|
||||
<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>
|
||||
<Button
|
||||
onclick={() => (opencrearUsuario = !opencrearUsuario)}
|
||||
|
||||
@@ -35,8 +35,5 @@
|
||||
<BotonSeguir post={{ authorId: usu.id }} />
|
||||
</div>
|
||||
</div>
|
||||
{#if usu.bio}
|
||||
<div class="mt-4 rounded-full bg-accent p-4 text-muted-foreground">{usu.bio}</div>
|
||||
{/if}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<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 href="/" class="mr-6 flex items-center space-x-2">
|
||||
<a data-sveltekit-preload-data={false} href="/" class="mr-6 flex items-center space-x-2">
|
||||
<Avatar
|
||||
class="h-8 w-8 transform rounded-sm! transition-transform duration-300 ease-in-out hover:scale-130 hover:rotate-12"
|
||||
>
|
||||
|
||||
21
src/lib/hooks/busquedaAdminUsuarios.ts
Normal file
21
src/lib/hooks/busquedaAdminUsuarios.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { apiBase } from '@/stores/url';
|
||||
import { sesionStore } from '@/stores/usuario';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
export async function busquedaAdminUsuarios(q: string) {
|
||||
try {
|
||||
const response = await fetch(get(apiBase) + '/api/admin/users?q=' + q, {
|
||||
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 { get } from "svelte/store";
|
||||
import { apiBase } from '@/stores/url';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
export async function checkEmail(email: string) {
|
||||
try {
|
||||
const req = await fetch(`${get(apiBase)}/api/users/check-email/${email}`, {
|
||||
method: "GET"
|
||||
});
|
||||
if (req.ok){
|
||||
return (await req.json()).available;
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const req = await fetch(`${get(apiBase)}/api/users/check-email/${email}`, {
|
||||
method: 'GET'
|
||||
});
|
||||
if (req.ok) {
|
||||
return (await req.json()).available;
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { apiBase } from "@/stores/url";
|
||||
import { get } from "svelte/store";
|
||||
import { apiBase } from '@/stores/url';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
export async function checkUsername(username: string) {
|
||||
try {
|
||||
const req = await fetch(`${get(apiBase)}/api/users/check-username/${username}`, {
|
||||
method: "GET"
|
||||
});
|
||||
if (req.ok){
|
||||
return (await req.json()).available;
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const req = await fetch(`${get(apiBase)}/api/users/check-username/${username}`, {
|
||||
method: 'GET'
|
||||
});
|
||||
if (req.ok) {
|
||||
return (await req.json()).available;
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { apiBase } from "@/stores/url";
|
||||
import { sesionStore } from "@/stores/usuario";
|
||||
import { get } from "svelte/store";
|
||||
import { apiBase } from '@/stores/url';
|
||||
import { sesionStore } from '@/stores/usuario';
|
||||
import { get } from 'svelte/store';
|
||||
import type { Post } from '../../types';
|
||||
import { PAGE_SIZE } from '../stores/posts';
|
||||
|
||||
@@ -10,12 +10,11 @@ export async function getPosts(page: number = 1): Promise<Post[]> {
|
||||
const headers: HeadersInit = {};
|
||||
if (token) headers.Authorization = `Bearer ${token}`;
|
||||
|
||||
const res = await fetch(
|
||||
`${get(apiBase)}/timeline?page=${page}&pageSize=${PAGE_SIZE}`,
|
||||
{ headers }
|
||||
);
|
||||
const res = await fetch(`${get(apiBase)}/timeline?page=${page}&pageSize=${PAGE_SIZE}`, {
|
||||
headers
|
||||
});
|
||||
|
||||
if (!res.ok) throw new Error('Error cargando posts');
|
||||
|
||||
return res.json();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { sesionStore } from '@/stores/usuario';
|
||||
import type { Post } from '../../types';
|
||||
|
||||
export async function likePost(post: Post) {
|
||||
let method = post.isLiked ? "DELETE" : "POST";
|
||||
let method = post.isLiked ? 'DELETE' : 'POST';
|
||||
try {
|
||||
const req = await fetch(get(apiBase) + `/api/posts/${post.id}/like`, {
|
||||
method: method,
|
||||
|
||||
@@ -24,7 +24,7 @@ export async function loadMorePosts() {
|
||||
if (newPosts.length < PAGE_SIZE) {
|
||||
finished = true;
|
||||
} else {
|
||||
page.update(p => p + 1);
|
||||
page.update((p) => p + 1);
|
||||
}
|
||||
} finally {
|
||||
loadingPosts.set(false);
|
||||
|
||||
@@ -6,12 +6,13 @@ import type { Post } from '../../types';
|
||||
export async function obtenerRespuestasPorId(
|
||||
id: string,
|
||||
fetch2?: Function,
|
||||
depends?: Function
|
||||
depends?: Function,
|
||||
page: number = 1
|
||||
): Promise<string | Post[] | null> {
|
||||
if (depends) depends('post:respuestas');
|
||||
const fetchFn = fetch2 ? fetch2 : fetch;
|
||||
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',
|
||||
headers: {
|
||||
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
import { sesionStore } from '@/stores/usuario';
|
||||
import type { UsersResponseDto } from '../../types';
|
||||
import { get } from 'svelte/store';
|
||||
import { apiBase } from '@/stores/url';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
export async function obtenerSeguidoresPorUsuario(
|
||||
id: string,
|
||||
page: number = 1,
|
||||
limit: number = 20,
|
||||
fetch2: Function
|
||||
fetch2?: Function
|
||||
): Promise<UsersResponseDto | null> {
|
||||
try {
|
||||
const fetchFunc = fetch2 || fetch;
|
||||
const response = await fetchFunc(`${get(apiBase)}/api/users/${id}/followers?limit=${limit}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const response = await fetchFunc(
|
||||
`${get(apiBase)}/api/users/${id}/followers?skip=${skip}&limit=${limit}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
|
||||
@@ -5,26 +5,31 @@ import { get } from 'svelte/store';
|
||||
|
||||
export async function obtenerSeguidosPorUsuario(
|
||||
id: string,
|
||||
page: number = 1,
|
||||
limit: number = 20,
|
||||
fetch2?: Function
|
||||
): Promise<UsersResponseDto | null> {
|
||||
try {
|
||||
const fetchFunc = fetch2 || fetch;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const response = await fetchFunc(`${get(apiBase)}/api/users/${id}/following?limit=${limit}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
||||
const response = await fetchFunc(
|
||||
`${get(apiBase)}/api/users/${id}/following?skip=${skip}&limit=${limit}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const users: UsersResponseDto = await response.json();
|
||||
return users;
|
||||
const data: UsersResponseDto = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import { addPost } from "@/stores/posts";
|
||||
import { apiBase } from "@/stores/url";
|
||||
import { sesionStore } from "@/stores/usuario";
|
||||
import { get } from "svelte/store";
|
||||
import { addPost } from '@/stores/posts';
|
||||
import { apiBase } from '@/stores/url';
|
||||
import { sesionStore } from '@/stores/usuario';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
export async function publicarPost(formData: FormData){
|
||||
try{
|
||||
const req = fetch(get(apiBase) + '/api/posts', {
|
||||
method: 'POST',
|
||||
//credentials: 'include',
|
||||
headers: {
|
||||
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
||||
},
|
||||
body: formData
|
||||
});
|
||||
export async function publicarPost(formData: FormData) {
|
||||
try {
|
||||
const req = fetch(get(apiBase) + '/api/posts', {
|
||||
method: 'POST',
|
||||
//credentials: 'include',
|
||||
headers: {
|
||||
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
||||
},
|
||||
body: formData
|
||||
});
|
||||
|
||||
const res = await req;
|
||||
if (res.ok) {
|
||||
const post = await res.json();
|
||||
addPost(post);
|
||||
return '';
|
||||
}
|
||||
return 'No se pudo crear el post.';
|
||||
} catch {
|
||||
return 'Fallo al alcanzar el servidor';
|
||||
const res = await req;
|
||||
if (res.ok) {
|
||||
const post = await res.json();
|
||||
addPost(post);
|
||||
return '';
|
||||
}
|
||||
return 'No se pudo crear el post.';
|
||||
} catch {
|
||||
return 'Fallo al alcanzar el servidor';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
export async function updateImagenDePerfil(){
|
||||
try{
|
||||
|
||||
}catch{
|
||||
|
||||
}
|
||||
export async function updateImagenDePerfil() {
|
||||
try {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import { sesionStore } from '@/stores/usuario';
|
||||
|
||||
export async function updatePost(post: Post, callbackfn: Function, message: string) {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("content", post.content);
|
||||
formData.append("image", post.image||"");
|
||||
const formData = new FormData();
|
||||
formData.append('content', post.content);
|
||||
formData.append('image', post.image || '');
|
||||
|
||||
const req = await fetch(get(apiBase) + `/api/posts/${post.id}`, {
|
||||
method: 'PUT',
|
||||
|
||||
@@ -19,21 +19,14 @@ export const addPost = (post: Post) => {
|
||||
posts.update((current) => [post, ...current]);
|
||||
};
|
||||
|
||||
export const updatePostStore = (
|
||||
postId: string,
|
||||
updatedData: Partial<Post>
|
||||
) => {
|
||||
export const updatePostStore = (postId: string, updatedData: Partial<Post>) => {
|
||||
posts.update((current) =>
|
||||
current.map((post) =>
|
||||
post.id === postId ? { ...post, ...updatedData } : post
|
||||
)
|
||||
current.map((post) => (post.id === postId ? { ...post, ...updatedData } : post))
|
||||
);
|
||||
};
|
||||
|
||||
export const removePost = (postId: string) => {
|
||||
posts.update((current) =>
|
||||
current.filter((post) => post.id !== postId)
|
||||
);
|
||||
posts.update((current) => current.filter((post) => post.id !== postId));
|
||||
};
|
||||
|
||||
export const resetPosts = () => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { get, writable } from 'svelte/store';
|
||||
import { browser } from '$app/environment';
|
||||
import type { Sesion } from '../../types';
|
||||
import { apiBase } from '@/stores/url';
|
||||
import { getFirebaseApp, getFirebaseAuth } from './firebase';
|
||||
|
||||
const { subscribe } = apiBase;
|
||||
let baseUrl: string = '';
|
||||
@@ -21,9 +22,9 @@ export const sesionStore = {
|
||||
reset: () => currentSesion.set(null)
|
||||
};
|
||||
|
||||
sesionStore.subscribe((value) => {
|
||||
console.log(value);
|
||||
});
|
||||
// sesionStore.subscribe((value) => {
|
||||
// console.log(value);
|
||||
// });
|
||||
|
||||
if (browser) {
|
||||
currentSesion.subscribe((value) => {
|
||||
@@ -54,9 +55,9 @@ if (browser) {
|
||||
|
||||
if (sesion.isFirebase) {
|
||||
try {
|
||||
// Simulamos la importación dinámica de Firebase
|
||||
const { getAuth } = await import('firebase/auth');
|
||||
const auth = getAuth();
|
||||
getFirebaseApp();
|
||||
|
||||
const auth = getFirebaseAuth();
|
||||
const user = auth.currentUser;
|
||||
|
||||
if (user) {
|
||||
|
||||
@@ -13,11 +13,11 @@ export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
|
||||
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };
|
||||
|
||||
export function filtrarImagen(file: File) {
|
||||
if (file) {
|
||||
if (file) {
|
||||
const allowed = ['image/png', 'image/jpg', 'image/jpeg', 'image/gif', 'image/webp'];
|
||||
if (allowed.includes(file.type)) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
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 { page } from '$app/state';
|
||||
import TablaUsuarios from '@/components/TablaUsuarios.svelte';
|
||||
import CardTitle from '@/components/ui/card/card-title.svelte';
|
||||
import CardHeader from '@/components/ui/card/card-header.svelte';
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { ModeWatcher } from 'mode-watcher';
|
||||
import Header from '@/head/Header.svelte';
|
||||
import { TooltipProvider } from '@/components/ui/tooltip';
|
||||
import { resolve } from '$app/paths';
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
@@ -17,3 +18,11 @@
|
||||
<TooltipProvider>
|
||||
{@render children()}
|
||||
</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">
|
||||
import { replaceState } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import { onMount } from 'svelte';
|
||||
import Card from '@/components/ui/card/card.svelte';
|
||||
import { Content } from '@/components/ui/card';
|
||||
import Spinner from '@/components/ui/spinner/spinner.svelte';
|
||||
@@ -11,21 +10,18 @@
|
||||
import PostCard from '@/components/PostCard.svelte';
|
||||
import ModalEditar from './[perfil]/modalEditar.svelte';
|
||||
import { sesionStore } from '@/stores/usuario';
|
||||
import {
|
||||
posts,
|
||||
updatePostStore,
|
||||
loadingPosts
|
||||
} from '@/stores/posts';
|
||||
import { posts, updatePostStore, loadingPosts, resetPosts } from '@/stores/posts';
|
||||
import { updatePost } from '@/hooks/updatePost';
|
||||
import { loadMorePosts } from '@/hooks/loadMorePosts';
|
||||
import type { Post } from '../types';
|
||||
import { fade, slide } from 'svelte/transition';
|
||||
|
||||
let postAModificar: Post | null = null;
|
||||
let mensajeError = '';
|
||||
let postAModificar: Post | null = $state(null);
|
||||
let mensajeError = $state('');
|
||||
let sentinel: HTMLDivElement;
|
||||
|
||||
onMount(() => {
|
||||
resetPosts();
|
||||
$effect(() => {
|
||||
loadMorePosts();
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
@@ -47,8 +43,7 @@
|
||||
|
||||
await updatePost(
|
||||
postAModificar,
|
||||
(postNuevo: Post) =>
|
||||
updatePostStore(postAModificar!.id, postNuevo),
|
||||
(postNuevo: Post) => updatePostStore(postAModificar!.id, postNuevo),
|
||||
mensajeError
|
||||
);
|
||||
postAModificar = null;
|
||||
@@ -63,9 +58,7 @@
|
||||
|
||||
{#if from === 'cambio_contraseña'}
|
||||
<Dialog open>
|
||||
<DialogContent>
|
||||
Se cambió la contraseña del usuario exitosamente
|
||||
</DialogContent>
|
||||
<DialogContent>Se cambió la contraseña del usuario exitosamente</DialogContent>
|
||||
</Dialog>
|
||||
{/if}
|
||||
|
||||
@@ -92,13 +85,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>
|
||||
<p class="text-center leading-7">No hay Posts que mostrar</p>
|
||||
</Content>
|
||||
</Card>
|
||||
{:else}
|
||||
@@ -120,9 +110,6 @@
|
||||
</div>
|
||||
{#if postAModificar}
|
||||
<div in:fade>
|
||||
<ModalEditar
|
||||
callbackfn={handleEditar}
|
||||
bind:post={postAModificar}
|
||||
/>
|
||||
<ModalEditar callbackfn={handleEditar} bind:post={postAModificar} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
import { resetPosts } from '@/stores/posts';
|
||||
|
||||
//export const ssr = true;
|
||||
// export async function load({}) {}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { apiBase } from '@/stores/url';
|
||||
import PenLine from '@lucide/svelte/icons/pen-line';
|
||||
import type { Post } from '../../types.js';
|
||||
import { fade, slide } from 'svelte/transition';
|
||||
import PostCard from '@/components/PostCard.svelte';
|
||||
@@ -9,7 +8,7 @@
|
||||
import ModalEditar from './modalEditar.svelte';
|
||||
import { page } from '$app/state';
|
||||
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 DialogContent from '@/components/ui/dialog/dialog-content.svelte';
|
||||
import DialogTitle from '@/components/ui/dialog/dialog-title.svelte';
|
||||
@@ -19,54 +18,108 @@
|
||||
import CardPerfil from '@/components/CardPerfil.svelte';
|
||||
import DialogModificarUsuario from '@/components/DialogModificarUsuario.svelte';
|
||||
import BotonSeguir from '@/components/BotonSeguir.svelte';
|
||||
import UserPen from '@lucide/svelte/icons/user-pen';
|
||||
import DialogResetPassword from '@/components/DialogResetPassword.svelte';
|
||||
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 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 postAModificar: Post | null = $state(null);
|
||||
|
||||
let showCrearPost = $state(false);
|
||||
|
||||
let data = $derived(page.data);
|
||||
$inspect(data);
|
||||
// $inspect(data);
|
||||
|
||||
$effect(() => {
|
||||
obtenerPosts();
|
||||
});
|
||||
let fetching = false;
|
||||
// svelte-ignore state_referenced_locally
|
||||
let currentProfile = $state(params.perfil);
|
||||
|
||||
async function obtenerPosts() {
|
||||
if (fetching || finished) return;
|
||||
|
||||
fetching = true;
|
||||
cargando = true;
|
||||
|
||||
try {
|
||||
const req = await fetch($apiBase + '/api/posts/user/' + params.perfil, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${$sesionStore?.accessToken}`
|
||||
const res = await fetch(
|
||||
`${$apiBase}/api/posts/user/${params.perfil}?page=${pageNumber}&pageSize=20`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${$sesionStore?.accessToken}`
|
||||
}
|
||||
}
|
||||
});
|
||||
if (req.ok) {
|
||||
setPosts(await req.json());
|
||||
);
|
||||
const nuevosPosts: Post[] = await res.json();
|
||||
|
||||
if (nuevosPosts.length === 0) {
|
||||
finished = true;
|
||||
return;
|
||||
}
|
||||
mensajeError = 'Fallo al obtener los datos';
|
||||
} catch {
|
||||
mensajeError = 'No se alcanzo el servidor';
|
||||
|
||||
posts.update((actuales = []) => [...actuales, ...nuevosPosts]);
|
||||
|
||||
pageNumber++;
|
||||
|
||||
if (nuevosPosts.length < 20) {
|
||||
finished = true;
|
||||
}
|
||||
} catch (error) {
|
||||
mensajeError = 'Error al cargar los posts';
|
||||
} finally {
|
||||
fetching = 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) {
|
||||
e.preventDefault();
|
||||
if (postAModificar == null) return;
|
||||
if (!postAModificar) return;
|
||||
|
||||
await updatePost(
|
||||
postAModificar,
|
||||
(postnuevo: Post) => updatePostStore(postAModificar!.id, postnuevo),
|
||||
|
||||
(postNuevo: Post) => updatePostStore(postAModificar!.id, postNuevo),
|
||||
mensajeError
|
||||
);
|
||||
|
||||
postAModificar = null;
|
||||
}
|
||||
</script>
|
||||
@@ -74,9 +127,9 @@
|
||||
<!-- {$inspect(data)} -->
|
||||
<div class="flex min-h-fit w-full items-center justify-center p-6 md:p-10">
|
||||
<div class="w-full max-w-6xl">
|
||||
{#key data}
|
||||
<CardPerfil bind:data />
|
||||
{/key}
|
||||
<!-- {#key data.id} -->
|
||||
<CardPerfil bind:data />
|
||||
<!-- {/key} -->
|
||||
<h1
|
||||
class="mt-10 flex scroll-m-20 justify-between text-3xl font-extrabold tracking-tight lg:text-3xl"
|
||||
>
|
||||
@@ -93,14 +146,12 @@
|
||||
<PenLine />
|
||||
</Button>
|
||||
{:else if $posts?.length == 0}
|
||||
<BotonSeguir post={{ authorId: data.id }} />
|
||||
<BotonSeguir post={{ authorId: data.id, id: data.id }} />
|
||||
{/if}
|
||||
</h1>
|
||||
|
||||
<hr class="mb-8" />
|
||||
{#if cargando}
|
||||
<CardCargando />
|
||||
{:else if mensajeError !== ''}
|
||||
{#if mensajeError !== ''}
|
||||
<CardError {mensajeError} />
|
||||
{:else}
|
||||
<div class="flex flex-col gap-2">
|
||||
@@ -109,6 +160,16 @@
|
||||
<PostCard {post} bind:postAModificar />
|
||||
</div>
|
||||
{/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>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -135,7 +196,7 @@
|
||||
</div>
|
||||
|
||||
{#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>
|
||||
<Button variant="default" size="icon-lg">
|
||||
<UserPen />
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
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 type { PageLoad } from './$types';
|
||||
import { obtenerSeguidosPorUsuario } from '@/hooks/obtenerSeguidosPorUsuario.js';
|
||||
import { obtenerSeguidoresPorUsuario } from '@/hooks/obtenerSeguidoresPorUsuario.js';
|
||||
import { obtenerCantidadDeSeguidores } from '@/hooks/obtenerCantidadDeSeguidores.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');
|
||||
const usuario: UserResponseDto | null = await obtenerUsuarioPorUsername(params.perfil, fetch);
|
||||
if (!usuario) error(404, 'No se encontro el usuario, ' + params.perfil);
|
||||
|
||||
const [seguidos, seguidores, countSeguidores, countSeguidos] = await Promise.all([
|
||||
obtenerSeguidosPorUsuario(usuario.id, 5, fetch),
|
||||
obtenerSeguidoresPorUsuario(usuario.id, 5, fetch),
|
||||
obtenerSeguidosPorUsuario(usuario.id, 1, 5, fetch),
|
||||
obtenerSeguidoresPorUsuario(usuario.id, 1, 5, fetch),
|
||||
obtenerCantidadDeSeguidores(usuario.id, fetch),
|
||||
obtenerCantidadDeSeguidos(usuario.id, fetch)
|
||||
]);
|
||||
@@ -25,4 +26,4 @@ export async function load({ params, depends, fetch }) {
|
||||
countSeguidores: countSeguidores.count,
|
||||
countSeguidos: countSeguidos.count
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,15 +1,40 @@
|
||||
<script lang="ts">
|
||||
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 UserCard from '@/components/UserCard.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { obtenerSeguidoresPorUsuario } from '@/hooks/obtenerSeguidoresPorUsuario';
|
||||
|
||||
type Data = {
|
||||
usuario: UserResponseDto;
|
||||
seguidores: UserResponseDto[];
|
||||
totalCount: number;
|
||||
};
|
||||
|
||||
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>
|
||||
|
||||
<div class="flex min-h-fit w-full items-center justify-center p-6 md:p-10">
|
||||
@@ -25,9 +50,14 @@
|
||||
<ArrowLeft />
|
||||
</button>
|
||||
</div>
|
||||
{#if data.seguidores.length === 0}
|
||||
|
||||
{#if isLoading}
|
||||
<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>
|
||||
{:else}
|
||||
<div class="flex flex-col sm:grid" style="grid-template-columns: repeat(2, 1fr); gap: 1rem;">
|
||||
@@ -38,5 +68,29 @@
|
||||
{/each}
|
||||
</div>
|
||||
{/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>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { obtenerSeguidoresPorUsuario } from '@/hooks/obtenerSeguidoresPorUsuario';
|
||||
import { obtenerUsuarioPorUsername } from '@/hooks/obtenerUsuario';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { PageLoad } 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);
|
||||
if (!usuario) error(404, 'No se encontro el usuario, ' + params.perfil);
|
||||
|
||||
const seguidoresResponse: UsersResponseDto | null = await obtenerSeguidoresPorUsuario(
|
||||
usuario.id,
|
||||
1,
|
||||
100,
|
||||
fetch
|
||||
);
|
||||
@@ -17,4 +19,4 @@ export async function load({ params, fetch }) {
|
||||
usuario,
|
||||
seguidores: seguidoresResponse?.response || []
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,15 +1,40 @@
|
||||
<script lang="ts">
|
||||
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 UserCard from '@/components/UserCard.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { obtenerSeguidosPorUsuario } from '@/hooks/obtenerSeguidosPorUsuario';
|
||||
|
||||
type Data = {
|
||||
usuario: UserResponseDto;
|
||||
seguidos: UserResponseDto[];
|
||||
totalCount: number;
|
||||
};
|
||||
|
||||
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>
|
||||
|
||||
<div class="flex min-h-fit w-full items-center justify-center p-6 md:p-10">
|
||||
@@ -25,7 +50,12 @@
|
||||
<ArrowLeft />
|
||||
</button>
|
||||
</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">
|
||||
<p>No hay seguidos para mostrar.</p>
|
||||
</div>
|
||||
@@ -38,5 +68,29 @@
|
||||
{/each}
|
||||
</div>
|
||||
{/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>
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
import { obtenerUsuarioPorUsername } from '@/hooks/obtenerUsuario';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { PageLoad } from './$types';
|
||||
import type { UserResponseDto, UsersResponseDto } from '../../../types';
|
||||
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);
|
||||
if (!usuario) error(404, 'No se encontro el usuario, ' + params.perfil);
|
||||
|
||||
const seguidosResponse: UsersResponseDto | null = await obtenerSeguidosPorUsuario(
|
||||
usuario.id,
|
||||
1,
|
||||
100,
|
||||
fetch
|
||||
);
|
||||
|
||||
return {
|
||||
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>
|
||||
@@ -25,6 +25,7 @@
|
||||
import TooltipContent from '@/components/ui/tooltip/tooltip-content.svelte';
|
||||
import { deletePost } from '@/hooks/deletePost';
|
||||
import { flip } from 'svelte/animate';
|
||||
import { obtenerRespuestasPorId } from '@/hooks/obtenerRespuestasPorId';
|
||||
|
||||
interface Prop {
|
||||
data: {
|
||||
@@ -36,7 +37,17 @@
|
||||
let tamaño = new TamañoPantalla();
|
||||
|
||||
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);
|
||||
|
||||
async function handleEditar(e: SubmitEvent) {
|
||||
@@ -104,7 +115,7 @@
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<!-- {#if tamaño.isMobile} -->
|
||||
<!-- {#if true} -->
|
||||
@@ -114,6 +125,26 @@
|
||||
<!-- {/if} -->
|
||||
</div>
|
||||
{/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>
|
||||
|
||||
@@ -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 w-full max-w-6xl flex-col gap-2">
|
||||
<h1 class="text-2xl font-bold">Usuarios</h1>
|
||||
<Separator></Separator>
|
||||
{#each data.usuarios as usu}
|
||||
<div class="w-full">
|
||||
<UserCard {usu} />
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<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>
|
||||
{#if data.usuarios.length != 0}
|
||||
<h1 class="text-2xl font-bold">Usuarios</h1>
|
||||
<Separator></Separator>
|
||||
{#each data.usuarios as usu}
|
||||
<div class="w-full">
|
||||
<UserCard {usu} />
|
||||
</div>
|
||||
{/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>
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ export async function load({ params }) {
|
||||
return error(500, 'No se pudo alcanzar el servidor.');
|
||||
}
|
||||
|
||||
if (usuarios.length == 0) {
|
||||
return error(404, 'No se encontraron usuarios que coinsidan con la busqueda.');
|
||||
if (usuarios.length == 0 && htags.length == 0) {
|
||||
return error(404, 'No se encontraron usuarios ni hashtags que coinsidan con la busqueda.');
|
||||
}
|
||||
return { usuarios, htags };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user