hecha funcionalidad completa del cambio de contraseña

This commit is contained in:
2026-01-13 22:13:59 -03:00
parent 56bfaa02cc
commit e44d15666c
5 changed files with 184 additions and 23 deletions

View File

@@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import { goto, replaceState } from '$app/navigation';
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 { sesionStore } from '@/stores/usuario'; import { sesionStore } from '@/stores/usuario';
@@ -11,6 +12,9 @@
import { fade, slide } from 'svelte/transition'; import { fade, slide } from 'svelte/transition';
import { getPosts } from '@/hooks/getPosts'; import { getPosts } from '@/hooks/getPosts';
import Spinner from '@/components/ui/spinner/spinner.svelte'; import Spinner from '@/components/ui/spinner/spinner.svelte';
import { page } from '$app/state';
import Dialog from '@/components/ui/dialog/dialog.svelte';
import DialogContent from '@/components/ui/dialog/dialog-content.svelte';
$effect(() => { $effect(() => {
resetPosts(); resetPosts();
@@ -33,8 +37,23 @@
); );
postAModificar = null; postAModificar = null;
} }
let from = $state(page.url.searchParams.get('from'));
$effect(() => {
goto('/', { replaceState: true });
});
</script> </script>
{#if from == 'cambio_contraseña'}
<Dialog
open={true}
onOpenChange={() => {
from = '';
}}
>
<DialogContent>Se cambio la contraseña del usuario exitosamente</DialogContent>
</Dialog>
{/if}
<svelte:head> <svelte:head>
<meta property="og:title" content="Mini-x" /> <meta property="og:title" content="Mini-x" />
<meta property="og:description" content="Pagina Principal" /> <meta property="og:description" content="Pagina Principal" />

View File

@@ -14,6 +14,7 @@
let estado: 'email' | 'otp' | 'nuevapass' = $state('email'); let estado: 'email' | 'otp' | 'nuevapass' = $state('email');
let email: string = $state(''); let email: string = $state('');
let otp: string = $state('');
</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">
@@ -22,10 +23,12 @@
<div class="mt-6"> <div class="mt-6">
{#if estado === 'email'} {#if estado === 'email'}
<IngresarEmail bind:estado bind:email /> <IngresarEmail bind:estado bind:email />
{:else if estado === 'otp'} {/if}
<Otp bind:estado /> {#if estado === 'otp'}
{:else if estado === 'nuevapass'} <Otp bind:estado {email} bind:otp />
<NuevaPass /> {/if}
{#if estado === 'nuevapass'}
<NuevaPass {otp} {email} />
{/if} {/if}
</div> </div>
</div> </div>

View File

@@ -1,10 +1,14 @@
<script lang="ts"> <script lang="ts">
import CardError from '@/components/CardError.svelte';
import Button from '@/components/ui/button/button.svelte'; import Button from '@/components/ui/button/button.svelte';
import CardContent from '@/components/ui/card/card-content.svelte'; import CardContent from '@/components/ui/card/card-content.svelte';
import Card from '@/components/ui/card/card.svelte'; import Card from '@/components/ui/card/card.svelte';
import DialogContent from '@/components/ui/dialog/dialog-content.svelte';
import Dialog from '@/components/ui/dialog/dialog.svelte';
import Input from '@/components/ui/input/input.svelte'; import Input from '@/components/ui/input/input.svelte';
import Spinner from '@/components/ui/spinner/spinner.svelte'; import Spinner from '@/components/ui/spinner/spinner.svelte';
import { checkEmail } from '@/hooks/checkEmail'; import { checkEmail } from '@/hooks/checkEmail';
import { apiBase } from '@/stores/url';
import Check from '@lucide/svelte/icons/check'; import Check from '@lucide/svelte/icons/check';
import Cross from '@lucide/svelte/icons/x'; import Cross from '@lucide/svelte/icons/x';
import { slide } from 'svelte/transition'; import { slide } from 'svelte/transition';
@@ -13,24 +17,43 @@
let checkeado = $state<Boolean | null>(null); let checkeado = $state<Boolean | null>(null);
let esEmailExistente = $state<boolean>(false); let esEmailExistente = $state<boolean>(false);
let mensajeError = $state('');
let lastemail: string;
$effect(() => { $effect(() => {
if (email == '') { if (email == '' || !email.includes('@')) {
checkeado = null; checkeado = null;
return; return;
} }
let timeoutId: ReturnType<typeof setTimeout> | undefined;
(async () => { (async () => {
checkeado = true; if (timeoutId) clearTimeout(timeoutId);
await Promise.all([checkEmaill(), new Promise((resolve) => setTimeout(resolve, 100))]); timeoutId = setTimeout(async () => {
checkeado = false; checkeado = true;
await checkEmaill();
checkeado = false;
}, 1000);
})(); })();
}); });
async function checkEmaill() { async function checkEmaill() {
esEmailExistente = !(await checkEmail(email)); try {
if (lastemail == email) return;
lastemail = email;
esEmailExistente = !(await checkEmail(email));
} catch {
esEmailExistente = false;
}
} }
</script> </script>
{#if mensajeError}
<Dialog open={!!mensajeError} onOpenChange={() => (mensajeError = '')}>
<DialogContent>
<CardError {mensajeError} />
</DialogContent>
</Dialog>
{/if}
<div transition:slide> <div transition:slide>
<Card> <Card>
<CardContent> <CardContent>
@@ -47,16 +70,31 @@
<Cross class="text-red-500" /> <Cross class="text-red-500" />
{/if} {/if}
</h2> </h2>
<Input type="email" placeholder="correo@ejemplo.com" bind:value={email} /> <form
<Button onsubmit={async (e) => {
disabled={!esEmailExistente} e.preventDefault();
onclick={async () => { try {
///WIP const formData = new FormData();
estado = 'otp'; formData.append('email', email);
const req = await fetch(`${$apiBase}/api/password-reset/otp`, {
method: 'POST',
body: formData
});
if (req.ok) {
estado = 'otp';
return;
}
mensajeError = await req.text();
} catch {
mensajeError = 'No se pudo alcanzar el servidor';
}
}} }}
> >
Enviar código <div class="flex flex-col gap-2">
</Button> <Input type="email" placeholder="correo@ejemplo.com" bind:value={email} />
<Button type="submit" disabled={!esEmailExistente}>Enviar código</Button>
</div>
</form>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>

View File

@@ -1,17 +1,66 @@
<script> <script>
import { goto } from '$app/navigation';
import CardError from '@/components/CardError.svelte';
import Button from '@/components/ui/button/button.svelte'; import Button from '@/components/ui/button/button.svelte';
import CardContent from '@/components/ui/card/card-content.svelte'; import CardContent from '@/components/ui/card/card-content.svelte';
import Card from '@/components/ui/card/card.svelte'; import Card from '@/components/ui/card/card.svelte';
import DialogContent from '@/components/ui/dialog/dialog-content.svelte';
import Dialog from '@/components/ui/dialog/dialog.svelte';
import Input from '@/components/ui/input/input.svelte'; import Input from '@/components/ui/input/input.svelte';
import Label from '@/components/ui/label/label.svelte'; import Label from '@/components/ui/label/label.svelte';
import { apiBase } from '@/stores/url';
import { sesionStore } from '@/stores/usuario';
import { slide } from 'svelte/transition'; import { slide } from 'svelte/transition';
let { otp, email } = $props();
let pass = $state('');
let pass2 = $state('');
let coinsiden = $derived(
pass === pass2 &&
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9])[A-Za-z\d\W_]*$/.test(pass) &&
pass.length > 8
);
let mensajeError = $state('');
</script> </script>
{#if mensajeError}
<Dialog open={!!mensajeError} onOpenChange={() => (mensajeError = '')}>
<DialogContent>
<CardError {mensajeError} />
</DialogContent>
</Dialog>
{/if}
<div transition:slide> <div transition:slide>
<Card> <Card>
<CardContent> <CardContent>
<h2 class="mb-4 text-2xl font-bold">Crear una Nueva Contraseña</h2> <h2 class="mb-4 text-2xl font-bold">Crear una Nueva Contraseña</h2>
<form onsubmit={() => {}}> <form
onsubmit={async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append('otp', otp);
formData.append('email', email);
formData.append('newpass', pass);
try {
const req = await fetch(`${$apiBase}/api/password-reset/change`, {
method: 'PATCH',
body: formData
});
if (req.ok) {
const token = await req.json();
sesionStore.set(token);
goto('/?from=cambio_contraseña');
return;
}
const data = await req.text();
mensajeError = data;
} catch {
mensajeError = 'No se pudo alcanzar el servidor';
}
}}
>
<div class="space-y-4"> <div class="space-y-4">
<div> <div>
<Label for="password" class="mb-1 block text-sm font-medium">Contraseña</Label> <Label for="password" class="mb-1 block text-sm font-medium">Contraseña</Label>
@@ -20,7 +69,12 @@
id="password" id="password"
class="w-full px-3 py-2" class="w-full px-3 py-2"
placeholder="Ingresa tu nueva contraseña" placeholder="Ingresa tu nueva contraseña"
bind:value={pass}
/> />
<p class="mt-2 text-sm text-gray-500">
La contraseña debe contener al menos una mayúscula, una minúscula, un número y un
carácter especial. Además de 8 chars de longitud.
</p>
</div> </div>
<div> <div>
<Label for="confirmPassword" class="mb-1 block text-sm font-medium" <Label for="confirmPassword" class="mb-1 block text-sm font-medium"
@@ -31,9 +85,10 @@
id="confirmPassword" id="confirmPassword"
class="w-full px-3 py-2" class="w-full px-3 py-2"
placeholder="Repite tu nueva contraseña" placeholder="Repite tu nueva contraseña"
bind:value={pass2}
/> />
</div> </div>
<Button type="submit">Establecer Contraseña</Button> <Button disabled={!coinsiden} type="submit">Establecer Contraseña</Button>
</div> </div>
</form> </form>
</CardContent> </CardContent>

View File

@@ -1,15 +1,38 @@
<script> <script>
import CardError from '@/components/CardError.svelte';
import Button from '@/components/ui/button/button.svelte'; import Button from '@/components/ui/button/button.svelte';
import CardContent from '@/components/ui/card/card-content.svelte'; import CardContent from '@/components/ui/card/card-content.svelte';
import Card from '@/components/ui/card/card.svelte'; import Card from '@/components/ui/card/card.svelte';
import DialogContent from '@/components/ui/dialog/dialog-content.svelte';
import Dialog from '@/components/ui/dialog/dialog.svelte';
import InputOtpGroup from '@/components/ui/input-otp/input-otp-group.svelte'; import InputOtpGroup from '@/components/ui/input-otp/input-otp-group.svelte';
import InputOtpSlot from '@/components/ui/input-otp/input-otp-slot.svelte'; import InputOtpSlot from '@/components/ui/input-otp/input-otp-slot.svelte';
import InputOtp from '@/components/ui/input-otp/input-otp.svelte'; import InputOtp from '@/components/ui/input-otp/input-otp.svelte';
import { apiBase } from '@/stores/url';
import { slide } from 'svelte/transition'; import { slide } from 'svelte/transition';
let { estado = $bindable() } = $props(); let { estado = $bindable(), email, otp = $bindable() } = $props();
let checkeado = $state(false);
let mensajeError = $state('');
$effect(() => {
if (otp && otp.length === 6) {
checkeado = true;
} else {
checkeado = false;
}
});
</script> </script>
{#if mensajeError}
<Dialog open={!!mensajeError} onOpenChange={() => (mensajeError = '')}>
<DialogContent>
<CardError {mensajeError} />
</DialogContent>
</Dialog>
{/if}
<div transition:slide> <div transition:slide>
<Card> <Card>
<CardContent> <CardContent>
@@ -21,10 +44,10 @@
<div class="space-y-4"> <div class="space-y-4">
<div class="flex justify-center"> <div class="flex justify-center">
<InputOtp maxlength={6}> <InputOtp maxlength={6} bind:value={otp}>
{#snippet children({ cells })} {#snippet children({ cells })}
<InputOtpGroup> <InputOtpGroup>
{#each cells as cell, i} {#each cells as cell}
<InputOtpSlot class="p-3!" {cell}></InputOtpSlot> <InputOtpSlot class="p-3!" {cell}></InputOtpSlot>
{/each} {/each}
</InputOtpGroup> </InputOtpGroup>
@@ -33,7 +56,30 @@
</div> </div>
<div class="flex justify-between"> <div class="flex justify-between">
<Button onclick={() => (estado = 'nuevapass')}>Verificar</Button> <Button
disabled={!checkeado}
onclick={async () => {
try {
const formData = new FormData();
formData.append('otp', otp);
formData.append('email', email);
let req = await fetch(`${$apiBase}/api/password-reset/otp/check`, {
method: 'POST',
body: formData
});
if (req.ok) {
estado = 'nuevapass';
return;
} else {
mensajeError = await req.text();
}
} catch {
mensajeError = 'No se pudo alcanzar el servidor';
}
}}
>
Verificar
</Button>
<Button variant="link" onclick={() => console.log('Reenviar código')}> <Button variant="link" onclick={() => console.log('Reenviar código')}>
Reenviar código de verificación Reenviar código de verificación