mirror of
https://github.com/emailerfacu-spec/minix-front.git
synced 2026-04-18 15:57:31 -03:00
Merge pull request #38 from emailerfacu-spec/dev
algunos refactors y caracteristicas como:
añadida pagina para ver htags
añadida pagina para ver resultados de busquedas
Ahora se puede editar el perfil propio en la pagina de perfil
This commit is contained in:
@@ -3,7 +3,10 @@
|
|||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "none",
|
"trailingComma": "none",
|
||||||
"printWidth": 100,
|
"printWidth": 100,
|
||||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
"plugins": [
|
||||||
|
"prettier-plugin-svelte",
|
||||||
|
"prettier-plugin-tailwindcss"
|
||||||
|
],
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": "*.svelte",
|
"files": "*.svelte",
|
||||||
|
|||||||
9
bun.lock
9
bun.lock
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
|
"configVersion": 0,
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mini-x-front",
|
"name": "mini-x-front",
|
||||||
@@ -15,9 +16,9 @@
|
|||||||
"@tailwindcss/vite": "^4.1.14",
|
"@tailwindcss/vite": "^4.1.14",
|
||||||
"bits-ui": "^2.11.0",
|
"bits-ui": "^2.11.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.7.4",
|
||||||
"prettier-plugin-svelte": "^3.4.0",
|
"prettier-plugin-svelte": "^3.4.0",
|
||||||
"prettier-plugin-tailwindcss": "^0.7.1",
|
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||||
"svelte": "^5.41.0",
|
"svelte": "^5.41.0",
|
||||||
"svelte-check": "^4.3.3",
|
"svelte-check": "^4.3.3",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
@@ -368,11 +369,11 @@
|
|||||||
|
|
||||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||||
|
|
||||||
"prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
|
"prettier": ["prettier@3.7.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA=="],
|
||||||
|
|
||||||
"prettier-plugin-svelte": ["prettier-plugin-svelte@3.4.0", "", { "peerDependencies": { "prettier": "^3.0.0", "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" } }, "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ=="],
|
"prettier-plugin-svelte": ["prettier-plugin-svelte@3.4.0", "", { "peerDependencies": { "prettier": "^3.0.0", "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" } }, "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ=="],
|
||||||
|
|
||||||
"prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.7.1", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-hermes": "*", "@prettier/plugin-oxc": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-hermes", "@prettier/plugin-oxc", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-svelte"] }, "sha512-Bzv1LZcuiR1Sk02iJTS1QzlFNp/o5l2p3xkopwOrbPmtMeh3fK9rVW5M3neBQzHq+kGKj/4LGQMTNcTH4NGPtQ=="],
|
"prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.7.2", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-hermes": "*", "@prettier/plugin-oxc": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-hermes", "@prettier/plugin-oxc", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-svelte"] }, "sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA=="],
|
||||||
|
|
||||||
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||||
|
|
||||||
|
|||||||
@@ -22,9 +22,9 @@
|
|||||||
"@tailwindcss/vite": "^4.1.14",
|
"@tailwindcss/vite": "^4.1.14",
|
||||||
"bits-ui": "^2.11.0",
|
"bits-ui": "^2.11.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.7.4",
|
||||||
"prettier-plugin-svelte": "^3.4.0",
|
"prettier-plugin-svelte": "^3.4.0",
|
||||||
"prettier-plugin-tailwindcss": "^0.7.1",
|
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||||
"svelte": "^5.41.0",
|
"svelte": "^5.41.0",
|
||||||
"svelte-check": "^4.3.3",
|
"svelte-check": "^4.3.3",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
|
|||||||
@@ -11,12 +11,16 @@
|
|||||||
import { updateUsuario } from '@/hooks/updateUsuario';
|
import { updateUsuario } from '@/hooks/updateUsuario';
|
||||||
import { sesionStore } from '@/stores/usuario';
|
import { sesionStore } from '@/stores/usuario';
|
||||||
import { obtenerUsuarioPorUsername } from '@/hooks/obtenerUsuario';
|
import { obtenerUsuarioPorUsername } from '@/hooks/obtenerUsuario';
|
||||||
|
import CardHeader from './ui/card/card-header.svelte';
|
||||||
|
import CardTitle from './ui/card/card-title.svelte';
|
||||||
|
import Badge from './ui/badge/badge.svelte';
|
||||||
|
|
||||||
let { data = $bindable() } = $props();
|
let { data = $bindable() } = $props();
|
||||||
|
|
||||||
let cargando = $state(false);
|
let cargando = $state(false);
|
||||||
let hoverimg = $state(false);
|
let hoverimg = $state(false);
|
||||||
let image: File | null = $state(null);
|
let image: File | null = $state(null);
|
||||||
|
let usu = $state({ displayName: data.displayName, bio: data.bio });
|
||||||
|
|
||||||
async function cambiarFotoDePerfil() {
|
async function cambiarFotoDePerfil() {
|
||||||
const input = document.createElement('input');
|
const input = document.createElement('input');
|
||||||
@@ -44,8 +48,8 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- <Button onclick={() => (cargando = !cargando)}>a</Button> -->
|
<!-- {$inspect(data)} -->
|
||||||
<Card class="mb-2 flex w-3/4 overflow-hidden">
|
<Card class="mb-2 flex overflow-hidden">
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{#if cargando}
|
{#if cargando}
|
||||||
<div class="flex flex-col items-center space-y-4">
|
<div class="flex flex-col items-center space-y-4">
|
||||||
@@ -54,38 +58,43 @@
|
|||||||
<Skeleton class="h-16 w-3/4 rounded-full" />
|
<Skeleton class="h-16 w-3/4 rounded-full" />
|
||||||
</div>
|
</div>
|
||||||
{:else if $sesionStore?.isAdmin || $sesionStore?.username == data.username}
|
{:else if $sesionStore?.isAdmin || $sesionStore?.username == data.username}
|
||||||
<button
|
<div class="flex w-full justify-center">
|
||||||
class="relative flex w-full items-center justify-center"
|
<button
|
||||||
onmouseenter={() => (hoverimg = true)}
|
class="relative flex items-center justify-center"
|
||||||
onmouseleave={() => (hoverimg = false)}
|
onmouseenter={() => (hoverimg = true)}
|
||||||
onclick={cambiarFotoDePerfil}
|
onmouseleave={() => (hoverimg = false)}
|
||||||
>
|
onclick={cambiarFotoDePerfil}
|
||||||
<Avatar
|
|
||||||
class={{
|
|
||||||
'brightness-0': hoverimg,
|
|
||||||
'relative z-0 mt-2 scale-250 border-2 border-slate-950 transition-all': true
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<AvatarImage src={data.imageUrl} alt="Imagen de perfil"></AvatarImage>
|
<Avatar
|
||||||
<AvatarFallback class="select-none">
|
class={{
|
||||||
{data.displayName?.[0]?.toUpperCase() || ''}
|
'brightness-0': hoverimg,
|
||||||
</AvatarFallback>
|
'relative z-0 mt-2 scale-250 border-2 border-slate-950 transition-all': true
|
||||||
</Avatar>
|
}}
|
||||||
<div
|
>
|
||||||
class="absolute inset-0 top-1/2 left-1/2 z-10 flex -translate-x-1/2 -translate-y-1/2 transform items-center justify-center"
|
<AvatarImage src={data.imageUrl} alt="Imagen de perfil"></AvatarImage>
|
||||||
class:opacity-100={hoverimg}
|
<AvatarFallback class="select-none">
|
||||||
class:opacity-0={!hoverimg}
|
{data.displayName?.[0]?.toUpperCase() || ''}
|
||||||
>
|
</AvatarFallback>
|
||||||
<Pen class="text-white" />
|
</Avatar>
|
||||||
</div>
|
<div
|
||||||
</button>
|
class="absolute inset-0 flex items-center justify-center"
|
||||||
|
class:opacity-100={hoverimg}
|
||||||
|
class:opacity-0={!hoverimg}
|
||||||
|
>
|
||||||
|
<Pen class="text-white" />
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h1 class="mt-10 scroll-m-20 text-center text-2xl font-extrabold tracking-tight lg:text-5xl">
|
<h1 class="mt-10 scroll-m-20 text-center text-2xl font-extrabold tracking-tight lg:text-5xl">
|
||||||
{data.displayName}
|
{usu.displayName}
|
||||||
<p class="ml-2 text-2xl font-medium text-muted-foreground">@{data.username}</p>
|
<p class="ml-2 text-2xl font-medium text-muted-foreground">@{data.username}</p>
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-4 rounded-full bg-accent p-4 text-center text-muted-foreground">
|
{#if usu.bio}
|
||||||
{data.bio}
|
<p class="mt-4 rounded-4xl bg-accent p-4 text-center text-muted-foreground">
|
||||||
</p>
|
{@html usu.bio.replaceAll('\n', '<br>')}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<div class="relative flex w-full items-center justify-center">
|
<div class="relative flex w-full items-center justify-center">
|
||||||
<Avatar
|
<Avatar
|
||||||
@@ -101,11 +110,54 @@
|
|||||||
</Avatar>
|
</Avatar>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="mt-10 scroll-m-20 text-center text-2xl font-extrabold tracking-tight lg:text-5xl">
|
<h1 class="mt-10 scroll-m-20 text-center text-2xl font-extrabold tracking-tight lg:text-5xl">
|
||||||
{data.displayName}
|
{usu.displayName}
|
||||||
|
<p class="ml-2 text-2xl font-medium text-muted-foreground">@{data.username}</p>
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-4 rounded-full bg-accent p-4 text-center text-muted-foreground">
|
{#if usu.bio}
|
||||||
{data.bio}
|
<p class="mt-4 rounded-4xl bg-accent p-4 text-center text-muted-foreground">
|
||||||
</p>
|
{@html usu.bio.replaceAll('\n', '<br>')}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
<div class="mt-2 flex gap-2">
|
||||||
|
<Card class="w-full">
|
||||||
|
<CardContent>
|
||||||
|
<CardHeader class="flex justify-between">
|
||||||
|
<CardTitle>Seguidos:</CardTitle>
|
||||||
|
<Badge variant="secondary">{data.seguidos.response.length}</Badge>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{#if data.seguidos.response.length === 0}
|
||||||
|
<h3>No hay Seguidos</h3>
|
||||||
|
{:else}
|
||||||
|
{#each data.seguidos.response as seguidos (seguidos.id)}
|
||||||
|
<p class="text-muted-foreground">
|
||||||
|
{seguidos.username}
|
||||||
|
</p>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</CardContent>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card class="w-full">
|
||||||
|
<CardContent>
|
||||||
|
<CardHeader class="flex justify-between">
|
||||||
|
<CardTitle>Seguidores:</CardTitle>
|
||||||
|
<Badge variant="secondary">{data.seguidores.response.length}</Badge>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{#if data.seguidores.response.length === 0}
|
||||||
|
<h3>No hay Seguidores</h3>
|
||||||
|
{:else}
|
||||||
|
{#each data.seguidores.response as seguidores (seguidores.id)}
|
||||||
|
<p class="text-muted-foreground">
|
||||||
|
{seguidores.username}
|
||||||
|
</p>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</CardContent>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
98
src/lib/components/DialogModificarUsuario.svelte
Normal file
98
src/lib/components/DialogModificarUsuario.svelte
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import UserPen from '@lucide/svelte/icons/user-pen';
|
||||||
|
import Button, { buttonVariants } from './ui/button/button.svelte';
|
||||||
|
import { Dialog } from './ui/dialog';
|
||||||
|
import DialogTrigger from './ui/dialog/dialog-trigger.svelte';
|
||||||
|
import DialogContent from './ui/dialog/dialog-content.svelte';
|
||||||
|
import DialogHeader from './ui/dialog/dialog-header.svelte';
|
||||||
|
import DialogTitle from './ui/dialog/dialog-title.svelte';
|
||||||
|
import type { UserResponseDto } from '../../types';
|
||||||
|
import Input from './ui/input/input.svelte';
|
||||||
|
import Field from './ui/field/field.svelte';
|
||||||
|
import FieldLabel from './ui/field/field-label.svelte';
|
||||||
|
import Textarea from './ui/textarea/textarea.svelte';
|
||||||
|
import FieldGroup from './ui/field/field-group.svelte';
|
||||||
|
import { updateUsuario } from '@/hooks/updateUsuario';
|
||||||
|
import DialogFooter from './ui/dialog/dialog-footer.svelte';
|
||||||
|
import Spinner from './ui/spinner/spinner.svelte';
|
||||||
|
import { invalidate, invalidateAll } from '$app/navigation';
|
||||||
|
import { page } from '$app/state';
|
||||||
|
|
||||||
|
let { data = $bindable() } = $props();
|
||||||
|
|
||||||
|
let usuario: UserResponseDto = $state({
|
||||||
|
id: data.id,
|
||||||
|
username: data.username,
|
||||||
|
displayName: data.displayName,
|
||||||
|
bio: data.bio,
|
||||||
|
profileImageUrl: data.profileImageUrl
|
||||||
|
});
|
||||||
|
|
||||||
|
let cargando = $state(false);
|
||||||
|
let open = $state(false);
|
||||||
|
|
||||||
|
async function onsubmit(e: SubmitEvent | null) {
|
||||||
|
if (e != null) e.preventDefault();
|
||||||
|
cargando = true;
|
||||||
|
await updateUsuario({
|
||||||
|
id: usuario.id,
|
||||||
|
profileImage: false,
|
||||||
|
bio: usuario.bio,
|
||||||
|
displayName: usuario.displayName,
|
||||||
|
profileImageUrl: usuario.profileImageUrl
|
||||||
|
});
|
||||||
|
cargando = false;
|
||||||
|
open = false;
|
||||||
|
// invalidateAll();
|
||||||
|
await invalidate(page.url);
|
||||||
|
await invalidate('perfil:general');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onkeydown(e: KeyboardEvent) {
|
||||||
|
if (e.ctrlKey && e.key === 'Enter') {
|
||||||
|
onsubmit(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dialog bind:open>
|
||||||
|
<DialogTrigger>
|
||||||
|
<div class="fixed right-8 bottom-8">
|
||||||
|
<Button variant="default" size="icon-lg">
|
||||||
|
<UserPen />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogTrigger>
|
||||||
|
<form {onsubmit}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>
|
||||||
|
<h1 class="text-2xl font-bold">Modificar Usuario</h1>
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<FieldGroup {onkeydown}>
|
||||||
|
<Field>
|
||||||
|
<FieldLabel>Nombre</FieldLabel>
|
||||||
|
<Input id="displayName" type="text" bind:value={usuario.displayName} required />
|
||||||
|
</Field>
|
||||||
|
<Field>
|
||||||
|
<FieldLabel>bio</FieldLabel>
|
||||||
|
<Textarea id="bio" bind:value={usuario.bio}></Textarea>
|
||||||
|
</Field>
|
||||||
|
<Field>
|
||||||
|
<FieldLabel>Email</FieldLabel>
|
||||||
|
<Input id="email" type="email" bind:value={usuario.email} />
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button type="submit">
|
||||||
|
{#if cargando}
|
||||||
|
<Spinner />
|
||||||
|
{:else}
|
||||||
|
Modificar
|
||||||
|
{/if}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
let contenido = $derived(() => {
|
let contenido = $derived(() => {
|
||||||
let t = post.content.replaceAll('\n', '<br>');
|
let t = post.content.replaceAll('\n', '<br>');
|
||||||
t = t.replace(
|
t = t.replace(
|
||||||
/#\w*/gm,
|
/#\p{L}*/u,
|
||||||
(match) =>
|
(match) =>
|
||||||
`<a class="hover:text-blue-200 text-blue-400" href="/htag/${match.replace('#', '')}">${match}</a>`
|
`<a class="hover:text-blue-200 text-blue-400" href="/htag/${match.replace('#', '')}">${match}</a>`
|
||||||
);
|
);
|
||||||
|
|||||||
22
src/lib/hooks/obtenerCantidadDeUsosdeHtag.ts
Normal file
22
src/lib/hooks/obtenerCantidadDeUsosdeHtag.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { apiBase } from '@/stores/url';
|
||||||
|
import { sesionStore } from '@/stores/usuario';
|
||||||
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
|
export async function obtenerCantidadDeUsosdeHtag(htag: string) {
|
||||||
|
if (!htag) return null;
|
||||||
|
try {
|
||||||
|
const req = await fetch(`${get(apiBase)}/api/posts/hashtag/${htag}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (req.ok) {
|
||||||
|
let data = await req.json();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +1,30 @@
|
|||||||
import { sesionStore } from "@/stores/usuario";
|
import { sesionStore } from '@/stores/usuario';
|
||||||
import type { UserResponseDto } from "../../types";
|
import type { UsersResponseDto } from '../../types';
|
||||||
import { get } from "svelte/store";
|
import { get } from 'svelte/store';
|
||||||
import { apiBase } from "@/stores/url";
|
import { apiBase } from '@/stores/url';
|
||||||
|
|
||||||
export async function obtenerSeguidoresPorUsuario(id: string, limit:number = 20): Promise<UserResponseDto[] | null> {
|
export async function obtenerSeguidoresPorUsuario(
|
||||||
try {
|
id: string,
|
||||||
const response = await fetch(`${get(apiBase)}/api/users/${id}/followers?limit=${limit}`, {
|
limit: number = 20,
|
||||||
method: 'GET',
|
fetch2: Function
|
||||||
headers: {
|
): Promise<UsersResponseDto | null> {
|
||||||
'Content-Type': 'application/json',
|
try {
|
||||||
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
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}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const followers: UserResponseDto[] = await response.json();
|
const followers: UsersResponseDto = await response.json();
|
||||||
return followers;
|
return followers;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,31 @@
|
|||||||
import { sesionStore } from "@/stores/usuario";
|
import { sesionStore } from '@/stores/usuario';
|
||||||
import type { UserResponseDto } from "../../types";
|
import type { UsersResponseDto } from '../../types';
|
||||||
import { apiBase } from "@/stores/url";
|
import { apiBase } from '@/stores/url';
|
||||||
import { get } from "svelte/store";
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
export async function obtenerSeguidosPorUsuario(id: string, limit:number = 20): Promise<UserResponseDto[] | null> {
|
export async function obtenerSeguidosPorUsuario(
|
||||||
try {
|
id: string,
|
||||||
const response = await fetch(`${get(apiBase)}/api/users/${id}/following?limit=${limit}`, {
|
limit: number = 20,
|
||||||
method: 'GET',
|
fetch2?: Function
|
||||||
headers: {
|
): Promise<UsersResponseDto | null> {
|
||||||
'Content-Type': 'application/json',
|
try {
|
||||||
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
const fetchFunc = fetch2 || fetch;
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
const response = await fetchFunc(`${get(apiBase)}/api/users/${id}/following?limit=${limit}`, {
|
||||||
return null;
|
method: 'GET',
|
||||||
}
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const users: UserResponseDto[] = await response.json();
|
if (!response.ok) {
|
||||||
return users;
|
return null;
|
||||||
} catch (error) {
|
}
|
||||||
return null;
|
|
||||||
}
|
const users: UsersResponseDto = await response.json();
|
||||||
|
return users;
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,14 @@ import type { UserResponseDto } from '../../types';
|
|||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import { sesionStore } from '@/stores/usuario';
|
import { sesionStore } from '@/stores/usuario';
|
||||||
|
|
||||||
export async function obtenerUsuarioPorUsername(username: string): Promise<UserResponseDto | null> {
|
export async function obtenerUsuarioPorUsername(
|
||||||
|
username: string,
|
||||||
|
fetch2?: Function
|
||||||
|
): Promise<UserResponseDto | null> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${get(apiBase)}/api/users/username/${username}`, {
|
const fetchFunction = fetch2 || fetch;
|
||||||
|
|
||||||
|
const response = await fetchFunction(`${get(apiBase)}/api/users/username/${username}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
@@ -1,48 +1,50 @@
|
|||||||
import { apiBase } from "@/stores/url"
|
import { apiBase } from '@/stores/url';
|
||||||
import { sesionStore } from "@/stores/usuario"
|
import { sesionStore } from '@/stores/usuario';
|
||||||
import { get } from "svelte/store"
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
export interface AdminUpdateUsuario {
|
export interface AdminUpdateUsuario {
|
||||||
id:string,
|
id: string;
|
||||||
displayName: string | null,
|
displayName: string | null;
|
||||||
bio: string | null,
|
bio: string | null;
|
||||||
profileImage:boolean,
|
profileImage: boolean;
|
||||||
image:File,
|
image: File;
|
||||||
profileImageUrl:string|null
|
profileImageUrl: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateUsuario(usuario: Partial<AdminUpdateUsuario>) {
|
export async function updateUsuario(usuario: Partial<AdminUpdateUsuario>) {
|
||||||
|
const formData = new FormData();
|
||||||
|
if (usuario.displayName) formData.append('displayName', usuario.displayName);
|
||||||
|
if (usuario.bio) formData.append('bio', usuario.bio);
|
||||||
|
if (usuario.profileImage) {
|
||||||
|
if (usuario.profileImageUrl) formData.append('profileImageUrl', 'null');
|
||||||
|
} else {
|
||||||
|
if (usuario.profileImageUrl) {
|
||||||
|
formData.append('profileImageUrl', usuario.profileImageUrl);
|
||||||
|
} else {
|
||||||
|
if (usuario.image) formData.append('profileImage', usuario.image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const formData = new FormData();
|
try {
|
||||||
if (usuario.displayName) formData.append('displayName', usuario.displayName);
|
const req = await fetch(get(apiBase) + '/api/users/' + usuario.id, {
|
||||||
if (usuario.bio) formData.append('bio', usuario.bio);
|
method: 'PUT',
|
||||||
if (usuario.image) formData.append('profileImage', usuario.image);
|
headers: {
|
||||||
if (usuario.profileImage){
|
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
||||||
if (usuario.profileImageUrl) formData.append('profileImageUrl', 'null');
|
},
|
||||||
}else{
|
body: formData
|
||||||
if (usuario.profileImageUrl) formData.append('profileImageUrl', usuario.profileImageUrl);
|
});
|
||||||
}
|
if (req.status === 204) {
|
||||||
|
let ret = {
|
||||||
|
// bio: usuario.bio,
|
||||||
|
displayName: usuario.displayName
|
||||||
|
// oldImageUrl: usuario.oldImageUrl,
|
||||||
|
};
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
const dataa = await req.json();
|
||||||
|
|
||||||
try {
|
return dataa.message;
|
||||||
const req = await fetch(get(apiBase) + "/api/users/"+usuario.id, {
|
} catch {
|
||||||
method: "PUT",
|
return 'No se pudo alcanzar el servidor';
|
||||||
headers: {
|
}
|
||||||
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
|
|
||||||
},
|
|
||||||
body: formData,
|
|
||||||
});
|
|
||||||
if (req.status === 204) {
|
|
||||||
let ret = {
|
|
||||||
// bio: usuario.bio,
|
|
||||||
displayName: usuario.displayName,
|
|
||||||
// oldImageUrl: usuario.oldImageUrl,
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
const dataa = await req.json();
|
|
||||||
|
|
||||||
return dataa.message;
|
|
||||||
} catch {
|
|
||||||
return "No se pudo alcanzar el servidor"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Card from '@/components/ui/card/card.svelte';
|
import Card from '@/components/ui/card/card.svelte';
|
||||||
import { Content } from '@/components/ui/card';
|
import { Content } from '@/components/ui/card';
|
||||||
import { apiBase } from '@/stores/url';
|
|
||||||
import { sesionStore } from '@/stores/usuario';
|
import { sesionStore } from '@/stores/usuario';
|
||||||
import CrearPost from '@/components/crear-post.svelte';
|
import CrearPost from '@/components/crear-post.svelte';
|
||||||
import { posts, setPosts, updatePostStore } from '@/stores/posts';
|
import { posts, setPosts, updatePostStore } from '@/stores/posts';
|
||||||
@@ -34,6 +33,14 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<meta property="og:title" content="Mini-x" />
|
||||||
|
<meta property="og:description" content="Pagina Principal" />
|
||||||
|
<meta property="og:image" content="https://tusitio.com/x.png" />
|
||||||
|
<meta property="og:url" content="https://minix-front.vercel.app/" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
<div class="flex min-h-fit w-full items-center justify-center p-6 md:p-10">
|
<div class="flex min-h-fit w-full items-center justify-center p-6 md:p-10">
|
||||||
<div class="w-full max-w-6xl">
|
<div class="w-full max-w-6xl">
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { apiBase } from '@/stores/url';
|
import { apiBase } from '@/stores/url';
|
||||||
import Ban from '@lucide/svelte/icons/ban';
|
|
||||||
import PenLine from '@lucide/svelte/icons/pen-line';
|
import PenLine from '@lucide/svelte/icons/pen-line';
|
||||||
import Card from '@/components/ui/card/card.svelte';
|
|
||||||
import Avatar from '@/components/ui/avatar/avatar.svelte';
|
|
||||||
import AvatarImage from '@/components/ui/avatar/avatar-image.svelte';
|
|
||||||
import AvatarFallback from '@/components/ui/avatar/avatar-fallback.svelte';
|
|
||||||
import { CardContent } from '@/components/ui/card';
|
|
||||||
import type { Post } from '../../types.js';
|
import type { Post } from '../../types.js';
|
||||||
import Spinner from '@/components/ui/spinner/spinner.svelte';
|
|
||||||
import { fade, slide } from 'svelte/transition';
|
import { fade, slide } from 'svelte/transition';
|
||||||
import PostCard from '@/components/PostCard.svelte';
|
import PostCard from '@/components/PostCard.svelte';
|
||||||
import { posts, setPosts, updatePostStore } from '@/stores/posts.js';
|
import { posts, setPosts, updatePostStore } from '@/stores/posts.js';
|
||||||
@@ -19,15 +12,12 @@
|
|||||||
import { Dialog } from '@/components/ui/dialog/index.js';
|
import { Dialog } from '@/components/ui/dialog/index.js';
|
||||||
import CrearPost from '@/components/crear-post.svelte';
|
import CrearPost from '@/components/crear-post.svelte';
|
||||||
import DialogContent from '@/components/ui/dialog/dialog-content.svelte';
|
import DialogContent from '@/components/ui/dialog/dialog-content.svelte';
|
||||||
import DialogHeader from '@/components/ui/dialog/dialog-header.svelte';
|
|
||||||
import DialogTitle from '@/components/ui/dialog/dialog-title.svelte';
|
import DialogTitle from '@/components/ui/dialog/dialog-title.svelte';
|
||||||
import { sesionStore } from '@/stores/usuario.js';
|
import { sesionStore } from '@/stores/usuario.js';
|
||||||
import CardHeader from '@/components/ui/card/card-header.svelte';
|
|
||||||
import CardTitle from '@/components/ui/card/card-title.svelte';
|
|
||||||
import Badge from '@/components/ui/badge/badge.svelte';
|
|
||||||
import CardCargando from '@/components/CardCargando.svelte';
|
import CardCargando from '@/components/CardCargando.svelte';
|
||||||
import CardError from '@/components/CardError.svelte';
|
import CardError from '@/components/CardError.svelte';
|
||||||
import CardPerfil from '@/components/CardPerfil.svelte';
|
import CardPerfil from '@/components/CardPerfil.svelte';
|
||||||
|
import DialogModificarUsuario from '@/components/DialogModificarUsuario.svelte';
|
||||||
|
|
||||||
let { params } = $props();
|
let { params } = $props();
|
||||||
|
|
||||||
@@ -38,13 +28,7 @@
|
|||||||
let showCrearPost = $state(false);
|
let showCrearPost = $state(false);
|
||||||
const toggleCrearPost = () => (showCrearPost = !showCrearPost);
|
const toggleCrearPost = () => (showCrearPost = !showCrearPost);
|
||||||
|
|
||||||
const { subscribe } = apiBase;
|
let data = $derived(page.data);
|
||||||
let baseUrl: string = '';
|
|
||||||
let data = $state(page.data);
|
|
||||||
|
|
||||||
subscribe((value) => {
|
|
||||||
baseUrl = value;
|
|
||||||
})();
|
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
obtenerPosts();
|
obtenerPosts();
|
||||||
@@ -52,7 +36,7 @@
|
|||||||
|
|
||||||
async function obtenerPosts() {
|
async function obtenerPosts() {
|
||||||
try {
|
try {
|
||||||
const req = await fetch(baseUrl + '/api/posts/user/' + params.perfil, {
|
const req = await fetch($apiBase + '/api/posts/user/' + params.perfil, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${$sesionStore?.accessToken}`
|
Authorization: `Bearer ${$sesionStore?.accessToken}`
|
||||||
@@ -83,52 +67,12 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- {$inspect(data)} -->
|
||||||
<div class="flex min-h-fit w-full items-center justify-center p-6 md:p-10">
|
<div class="flex min-h-fit w-full items-center justify-center p-6 md:p-10">
|
||||||
<div class="w-full max-w-6xl">
|
<div class="w-full max-w-6xl">
|
||||||
<div class="flex gap-2">
|
{#key data}
|
||||||
<CardPerfil bind:data />
|
<CardPerfil bind:data />
|
||||||
<aside class="flex w-1/4 flex-col gap-2">
|
{/key}
|
||||||
<Card class="w-full">
|
|
||||||
<CardContent>
|
|
||||||
<CardHeader class="flex justify-between">
|
|
||||||
<CardTitle>Seguidos:</CardTitle>
|
|
||||||
<Badge variant="secondary">{data.seguidos.length}</Badge>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
{#if data.seguidos.length === 0}
|
|
||||||
<h3>No hay Seguidos</h3>
|
|
||||||
{:else}
|
|
||||||
{#each data.seguidos as seguidos (seguidos.id)}
|
|
||||||
<p class="text-muted-foreground">
|
|
||||||
{seguidos.username}
|
|
||||||
</p>
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</CardContent>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<Card class="w-full">
|
|
||||||
<CardContent>
|
|
||||||
<CardHeader class="flex justify-between">
|
|
||||||
<CardTitle>Seguidores:</CardTitle>
|
|
||||||
<Badge variant="secondary">{data.seguidores.length}</Badge>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
{#if data.seguidores.length === 0}
|
|
||||||
<h3>No hay Seguidores</h3>
|
|
||||||
{:else}
|
|
||||||
{#each data.seguidores as seguidores (seguidores.id)}
|
|
||||||
<p class="text-muted-foreground">
|
|
||||||
{seguidores.username}
|
|
||||||
</p>
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</CardContent>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</aside>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h1
|
<h1
|
||||||
class="mt-10 flex scroll-m-20 justify-between text-3xl font-extrabold tracking-tight lg:text-3xl"
|
class="mt-10 flex scroll-m-20 justify-between text-3xl font-extrabold tracking-tight lg:text-3xl"
|
||||||
>
|
>
|
||||||
@@ -182,3 +126,15 @@
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if $sesionStore?.isAdmin || $sesionStore?.username == params.perfil}
|
||||||
|
<DialogModificarUsuario bind:data />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<meta property="og:title" content="Mini-x" />
|
||||||
|
<meta property="og:description" content={`viendo el perfil de @${data.username}`} />
|
||||||
|
<meta property="og:image" content={data.imageUrl} />
|
||||||
|
<meta property="og:url" content="https://minix-front.vercel.app/" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
</svelte:head>
|
||||||
|
|||||||
@@ -4,12 +4,15 @@ import { error } from '@sveltejs/kit';
|
|||||||
import { obtenerSeguidosPorUsuario } from '@/hooks/obtenerSeguidosPorUsuario.js';
|
import { obtenerSeguidosPorUsuario } from '@/hooks/obtenerSeguidosPorUsuario.js';
|
||||||
import { obtenerSeguidoresPorUsuario } from '@/hooks/obtenerSeguidoresPorUsuario.js';
|
import { obtenerSeguidoresPorUsuario } from '@/hooks/obtenerSeguidoresPorUsuario.js';
|
||||||
|
|
||||||
export async function load({ params }) {
|
export async function load({ params, depends, fetch }) {
|
||||||
const usuario: UserResponseDto | null = await obtenerUsuarioPorUsername(params.perfil);
|
depends('perfil:general');
|
||||||
if(!usuario) error(404, 'No se encontro el usuario, ' + params.perfil);
|
const usuario: UserResponseDto | null = await obtenerUsuarioPorUsername(params.perfil, fetch);
|
||||||
|
if (!usuario) error(404, 'No se encontro el usuario, ' + params.perfil);
|
||||||
|
|
||||||
const seguidos = await obtenerSeguidosPorUsuario(usuario.id, 3);
|
const [seguidos, seguidores] = await Promise.all([
|
||||||
const seguidores = await obtenerSeguidoresPorUsuario(usuario.id, 3);
|
obtenerSeguidosPorUsuario(usuario.id, 3, fetch),
|
||||||
|
obtenerSeguidoresPorUsuario(usuario.id, 3, fetch)
|
||||||
|
]);
|
||||||
|
|
||||||
return { ...usuario, seguidos, seguidores };
|
return { ...usuario, seguidos, seguidores };
|
||||||
}
|
}
|
||||||
|
|||||||
89
src/routes/htag/[htag]/+page.svelte
Normal file
89
src/routes/htag/[htag]/+page.svelte
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import PostCard from '@/components/PostCard.svelte';
|
||||||
|
import CardContent from '@/components/ui/card/card-content.svelte';
|
||||||
|
import CardTitle from '@/components/ui/card/card-title.svelte';
|
||||||
|
import Card from '@/components/ui/card/card.svelte';
|
||||||
|
import type { Post } from '../../../types.js';
|
||||||
|
import ModalEditar from '../../[perfil]/modalEditar.svelte';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
import { updatePostStore } from '@/stores/posts';
|
||||||
|
import { updatePost } from '@/hooks/updatePost';
|
||||||
|
import Separator from '@/components/ui/separator/separator.svelte';
|
||||||
|
import { page } from '$app/state';
|
||||||
|
|
||||||
|
interface props {
|
||||||
|
data: {
|
||||||
|
htag: string;
|
||||||
|
posts: {
|
||||||
|
count: number;
|
||||||
|
response: [Post];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let { data }: props = $props();
|
||||||
|
|
||||||
|
let posts = $state(data.posts.response);
|
||||||
|
let postAModificar: Post | null = $state(null);
|
||||||
|
|
||||||
|
let postsfiltro = $derived(
|
||||||
|
posts.filter((x) => {
|
||||||
|
const regex = new RegExp(`#${data.htag}\\b`, 'gm');
|
||||||
|
return regex.test(x.content);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
async function handleEditar(e: SubmitEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (postAModificar == null) return;
|
||||||
|
await updatePost(
|
||||||
|
postAModificar,
|
||||||
|
(postnuevo: Post) => updatePostStore(postAModificar!.id, postnuevo),
|
||||||
|
|
||||||
|
''
|
||||||
|
);
|
||||||
|
postAModificar = null;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex min-h-fit w-full flex-col items-center justify-center pt-2">
|
||||||
|
<div class=" w-full max-w-6xl flex-col items-center">
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<Card class="w-fit">
|
||||||
|
<CardContent>
|
||||||
|
<CardTitle>
|
||||||
|
<h1
|
||||||
|
class="mb-2 scroll-m-20 text-center text-4xl font-extrabold tracking-tight text-blue-500 lg:text-5xl"
|
||||||
|
>
|
||||||
|
#{data.htag}
|
||||||
|
</h1>
|
||||||
|
<Separator></Separator>
|
||||||
|
</CardTitle>
|
||||||
|
<p>
|
||||||
|
El hashtag se ha utilizado {data.posts.count} ve{#if data.posts.count > 1}ces{:else}z{/if}
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
<hr class="my-2" />
|
||||||
|
|
||||||
|
<div class="mt-1 flex flex-col gap-3">
|
||||||
|
{#each postsfiltro as post}
|
||||||
|
<PostCard {post} bind:postAModificar />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if postAModificar}
|
||||||
|
<div in:fade>
|
||||||
|
<ModalEditar callbackfn={handleEditar} bind:post={postAModificar} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<meta property="og:title" content="Mini-x" />
|
||||||
|
<meta property="og:description" content={`Buscando #${page.params.htag}`} />
|
||||||
|
<meta property="og:image" content="https://minix-front.vercel.app/x.png" />
|
||||||
|
<meta property="og:url" content="https://minix-front.vercel.app/" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
</svelte:head>
|
||||||
10
src/routes/htag/[htag]/+page.ts
Normal file
10
src/routes/htag/[htag]/+page.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { obtenerCantidadDeUsosdeHtag } from '@/hooks/obtenerCantidadDeUsosdeHtag.js';
|
||||||
|
import { error } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export async function load({ params }) {
|
||||||
|
let { htag } = params;
|
||||||
|
|
||||||
|
const posts = await obtenerCantidadDeUsosdeHtag(htag);
|
||||||
|
// if (cantidad == null || posts.lenght == 0) return error(404, 'no existe el #(hashtag)');
|
||||||
|
return { htag, posts };
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
export function load({ url }) {
|
export function load({ url }) {
|
||||||
return {
|
return {
|
||||||
message: url.searchParams.get('msg')
|
message: url.searchParams.get('msg')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +1,62 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as Alert from '@/components/ui/alert';
|
import * as Alert from '@/components/ui/alert';
|
||||||
import LoginForm from '@/components/ui/login-form/login-form.svelte';
|
import LoginForm from '@/components/ui/login-form/login-form.svelte';
|
||||||
import AlertCircleIcon from "@lucide/svelte/icons/alert-circle";
|
import AlertCircleIcon from '@lucide/svelte/icons/alert-circle';
|
||||||
import { fade, fly } from 'svelte/transition';
|
import { fade, fly } from 'svelte/transition';
|
||||||
import Info from '@lucide/svelte/icons/info';
|
import Info from '@lucide/svelte/icons/info';
|
||||||
|
|
||||||
let {data} = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
let showAlert: boolean = $state(false);
|
let showAlert: boolean = $state(false);
|
||||||
|
|
||||||
let message = $state(data.message);
|
let message = $state(data.message);
|
||||||
|
|
||||||
$effect(()=>{
|
$effect(() => {
|
||||||
resetAlert();
|
resetAlert();
|
||||||
if (data.message) {
|
if (data.message) {
|
||||||
history.replaceState(history.state, "", "/login");
|
history.replaceState(history.state, '', '/login');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
message = "";
|
message = '';
|
||||||
}, 7000);
|
}, 7000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function resetAlert (){
|
|
||||||
if (showAlert == true){
|
|
||||||
await new Promise(res => setTimeout(res, 2000));
|
|
||||||
showAlert=false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
async function resetAlert() {
|
||||||
|
if (showAlert == true) {
|
||||||
|
await new Promise((res) => setTimeout(res, 2000));
|
||||||
|
showAlert = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<div class="flex min-h-fit w-full items-center justify-center p-6 md:p-10">
|
<div class="flex min-h-fit w-full items-center justify-center p-6 md:p-10">
|
||||||
<div class="w-full max-w-sm">
|
<div class="w-full max-w-sm">
|
||||||
<div class="w-full max-w-sm">
|
{#if message}
|
||||||
{#if message}
|
<div class="mb-2" transition:fly>
|
||||||
<div class="mb-2" transition:fly>
|
<Alert.Root>
|
||||||
<Alert.Root>
|
<Info />
|
||||||
<Info />
|
<Alert.Title>Info</Alert.Title>
|
||||||
<Alert.Title>Info</Alert.Title>
|
<Alert.Description>Ingrese las credenciales de la cuenta recien creada</Alert.Description>
|
||||||
<Alert.Description>
|
</Alert.Root>
|
||||||
Ingrese las credenciales de la cuenta recien creada
|
</div>
|
||||||
</Alert.Description>
|
{/if}
|
||||||
</Alert.Root>
|
<LoginForm bind:showAlert id="1" />
|
||||||
</div>
|
{#if showAlert}
|
||||||
|
<div class="mt-2" transition:fade>
|
||||||
{/if}
|
<Alert.Root variant="destructive">
|
||||||
<LoginForm bind:showAlert={showAlert} id="1" />
|
<AlertCircleIcon />
|
||||||
{#if showAlert}
|
<Alert.Title>No se pudo iniciar sesion</Alert.Title>
|
||||||
<div class="mt-2" transition:fade>
|
<Alert.Description>Revise su usuario o contraseña</Alert.Description>
|
||||||
<Alert.Root variant="destructive">
|
</Alert.Root>
|
||||||
<AlertCircleIcon />
|
</div>
|
||||||
<Alert.Title>No se pudo iniciar sesion</Alert.Title>
|
{/if}
|
||||||
<Alert.Description>
|
|
||||||
Revise su usuario o contraseña
|
|
||||||
</Alert.Description>
|
|
||||||
</Alert.Root>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<meta property="og:title" content="Mini-x" />
|
||||||
|
<meta property="og:description" content={`Inicia Sesion`} />
|
||||||
|
<meta property="og:image" content="https://minix-front.vercel.app/x.png" />
|
||||||
|
<meta property="og:url" content="https://minix-front.vercel.app/" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
</svelte:head>
|
||||||
|
|||||||
@@ -1,38 +1,42 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import SignupForm from '@/components/signup-form.svelte';
|
import SignupForm from '@/components/signup-form.svelte';
|
||||||
|
import AlertCircleIcon from '@lucide/svelte/icons/alert-circle';
|
||||||
import AlertCircleIcon from "@lucide/svelte/icons/alert-circle";
|
import * as Alert from '@/components/ui/alert';
|
||||||
import * as Alert from '@/components/ui/alert';
|
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
let showAlert: boolean = $state(false);
|
let showAlert: boolean = $state(false);
|
||||||
|
|
||||||
$effect(()=>{
|
$effect(() => {
|
||||||
resetAlert();
|
resetAlert();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function resetAlert (){
|
|
||||||
if (showAlert == true){
|
|
||||||
await new Promise(res => setTimeout(res, 2000));
|
|
||||||
showAlert=false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
async function resetAlert() {
|
||||||
|
if (showAlert == true) {
|
||||||
|
await new Promise((res) => setTimeout(res, 2000));
|
||||||
|
showAlert = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex min-h-fit w-full items-center justify-center p-6 md:p-10">
|
<div class="flex min-h-fit w-full items-center justify-center p-6 md:p-10">
|
||||||
<div class="w-full max-w-sm">
|
<div class="w-full max-w-sm">
|
||||||
<SignupForm bind:showAlert={showAlert} />
|
<SignupForm bind:showAlert />
|
||||||
{#if showAlert}
|
{#if showAlert}
|
||||||
<div class="mt-2" transition:fade>
|
<div class="mt-2" transition:fade>
|
||||||
<Alert.Root variant="destructive">
|
<Alert.Root variant="destructive">
|
||||||
<AlertCircleIcon />
|
<AlertCircleIcon />
|
||||||
<Alert.Title>No se pudo crear la cuenta</Alert.Title>
|
<Alert.Title>No se pudo crear la cuenta</Alert.Title>
|
||||||
<Alert.Description>
|
<Alert.Description>Intente nuevamente.</Alert.Description>
|
||||||
Intente nuevamente.
|
</Alert.Root>
|
||||||
</Alert.Description>
|
</div>
|
||||||
</Alert.Root>
|
{/if}
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<meta property="og:title" content="Mini-x" />
|
||||||
|
<meta property="og:description" content={`Registra un usuario`} />
|
||||||
|
<meta property="og:image" content="https://minix-front.vercel.app/x.png" />
|
||||||
|
<meta property="og:url" content="https://minix-front.vercel.app/" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
</svelte:head>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { page } from '$app/state';
|
||||||
import UserCard from '@/components/UserCard.svelte';
|
import UserCard from '@/components/UserCard.svelte';
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -9,3 +9,11 @@
|
|||||||
<UserCard {usu} />
|
<UserCard {usu} />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<meta property="og:title" content="Mini-x" />
|
||||||
|
<meta property="og:description" content={`Buscando ${page.params.user}`} />
|
||||||
|
<meta property="og:image" content="https://minix-front.vercel.app/x.png" />
|
||||||
|
<meta property="og:url" content="https://minix-front.vercel.app/" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
</svelte:head>
|
||||||
|
|||||||
28
src/types.d.ts
vendored
28
src/types.d.ts
vendored
@@ -84,18 +84,34 @@ export interface UserResponseDto {
|
|||||||
displayName: string;
|
displayName: string;
|
||||||
email: string;
|
email: string;
|
||||||
bio: string;
|
bio: string;
|
||||||
imageUrl: string?;
|
imageUrl: string?;
|
||||||
profileImageUrl: string;
|
profileImageUrl: string;
|
||||||
followersCount: number;
|
followersCount: number;
|
||||||
followingCount: number;
|
followingCount: number;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
postsCount: number;
|
postsCount: number;
|
||||||
}
|
}
|
||||||
|
export interface UsersResponseDto {
|
||||||
|
response: {
|
||||||
|
id: string;
|
||||||
|
username: string;
|
||||||
|
displayName: string;
|
||||||
|
email: string;
|
||||||
|
bio: string;
|
||||||
|
imageUrl?: string;
|
||||||
|
profileImageUrl: string;
|
||||||
|
followersCount: number;
|
||||||
|
followingCount: number;
|
||||||
|
createdAt: string;
|
||||||
|
postsCount: number;
|
||||||
|
}[];
|
||||||
|
totalCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface UpdateUserRequest {
|
export interface UpdateUserRequest {
|
||||||
username: string?;
|
username: string?;
|
||||||
displayName: string?;
|
displayName: string?;
|
||||||
bio: string?;
|
bio: string?;
|
||||||
email: string?;
|
email: string?;
|
||||||
profileImage: File?;
|
profileImage: File?;
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
static/x.png
Normal file
BIN
static/x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
Reference in New Issue
Block a user