mirror of
https://github.com/emailerfacu-spec/minix-front.git
synced 2026-04-30 17:42:46 -03:00
Merge pull request #76 from emailerfacu-spec/reset-contraseña
Reset contraseña
This commit is contained in:
@@ -0,0 +1,23 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn, type WithElementRef, type WithoutChildren } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: WithoutChildren<WithElementRef<HTMLAttributes<HTMLSpanElement>>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="breadcrumb-ellipsis"
|
||||||
|
role="presentation"
|
||||||
|
aria-hidden="true"
|
||||||
|
class={cn("flex size-9 items-center justify-center", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
<EllipsisIcon class="size-4" />
|
||||||
|
<span class="sr-only">More</span>
|
||||||
|
</span>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLLiAttributes } from "svelte/elements";
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLLiAttributes> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<li
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="breadcrumb-item"
|
||||||
|
class={cn("inline-flex items-center gap-1.5", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</li>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAnchorAttributes } from "svelte/elements";
|
||||||
|
import type { Snippet } from "svelte";
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
href = undefined,
|
||||||
|
child,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAnchorAttributes> & {
|
||||||
|
child?: Snippet<[{ props: HTMLAnchorAttributes }]>;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
const attrs = $derived({
|
||||||
|
"data-slot": "breadcrumb-link",
|
||||||
|
class: cn("hover:text-foreground transition-colors", className),
|
||||||
|
href,
|
||||||
|
...restProps,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if child}
|
||||||
|
{@render child({ props: attrs })}
|
||||||
|
{:else}
|
||||||
|
<a bind:this={ref} {...attrs}>
|
||||||
|
{@render children?.()}
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLOlAttributes } from "svelte/elements";
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLOlAttributes> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ol
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="breadcrumb-list"
|
||||||
|
class={cn(
|
||||||
|
"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</ol>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="breadcrumb-page"
|
||||||
|
role="link"
|
||||||
|
aria-disabled="true"
|
||||||
|
aria-current="page"
|
||||||
|
class={cn("text-foreground font-normal", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</span>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
import type { HTMLLiAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLLiAttributes> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<li
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="breadcrumb-separator"
|
||||||
|
role="presentation"
|
||||||
|
aria-hidden="true"
|
||||||
|
class={cn("[&>svg]:size-3.5", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{#if children}
|
||||||
|
{@render children?.()}
|
||||||
|
{:else}
|
||||||
|
<ChevronRightIcon />
|
||||||
|
{/if}
|
||||||
|
</li>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { WithElementRef } from "$lib/utils.js";
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<nav
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="breadcrumb"
|
||||||
|
class={className}
|
||||||
|
aria-label="breadcrumb"
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</nav>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import Root from "./breadcrumb.svelte";
|
||||||
|
import Ellipsis from "./breadcrumb-ellipsis.svelte";
|
||||||
|
import Item from "./breadcrumb-item.svelte";
|
||||||
|
import Separator from "./breadcrumb-separator.svelte";
|
||||||
|
import Link from "./breadcrumb-link.svelte";
|
||||||
|
import List from "./breadcrumb-list.svelte";
|
||||||
|
import Page from "./breadcrumb-page.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
Ellipsis,
|
||||||
|
Item,
|
||||||
|
Separator,
|
||||||
|
Link,
|
||||||
|
List,
|
||||||
|
Page,
|
||||||
|
//
|
||||||
|
Root as Breadcrumb,
|
||||||
|
Ellipsis as BreadcrumbEllipsis,
|
||||||
|
Item as BreadcrumbItem,
|
||||||
|
Separator as BreadcrumbSeparator,
|
||||||
|
Link as BreadcrumbLink,
|
||||||
|
List as BreadcrumbList,
|
||||||
|
Page as BreadcrumbPage,
|
||||||
|
};
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import Root from "./input-otp.svelte";
|
||||||
|
import Group from "./input-otp-group.svelte";
|
||||||
|
import Slot from "./input-otp-slot.svelte";
|
||||||
|
import Separator from "./input-otp-separator.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
Group,
|
||||||
|
Slot,
|
||||||
|
Separator,
|
||||||
|
Root as InputOTP,
|
||||||
|
Group as InputOTPGroup,
|
||||||
|
Slot as InputOTPSlot,
|
||||||
|
Separator as InputOTPSeparator,
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="input-otp-group"
|
||||||
|
class={cn("flex items-center", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import type { WithElementRef } from "$lib/utils.js";
|
||||||
|
import MinusIcon from "@lucide/svelte/icons/minus";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={ref} data-slot="input-otp-separator" role="separator" {...restProps}>
|
||||||
|
{#if children}
|
||||||
|
{@render children?.()}
|
||||||
|
{:else}
|
||||||
|
<MinusIcon />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { PinInput as InputOTPPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
cell,
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: InputOTPPrimitive.CellProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<InputOTPPrimitive.Cell
|
||||||
|
{cell}
|
||||||
|
bind:ref
|
||||||
|
data-slot="input-otp-slot"
|
||||||
|
class={cn(
|
||||||
|
"border-input aria-invalid:border-destructive dark:bg-input/30 relative flex size-9 items-center justify-center border-y border-e text-sm transition-all outline-none first:rounded-s-md first:border-s last:rounded-e-md",
|
||||||
|
cell.isActive &&
|
||||||
|
"border-ring ring-ring/50 aria-invalid:border-destructive dark:aria-invalid:ring-destructive/40 aria-invalid:ring-destructive/20 ring-offset-background z-10 ring-[3px]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{cell.char}
|
||||||
|
{#if cell.hasFakeCaret}
|
||||||
|
<div class="pointer-events-none absolute inset-0 flex items-center justify-center">
|
||||||
|
<div class="animate-caret-blink bg-foreground h-4 w-px duration-1000"></div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</InputOTPPrimitive.Cell>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { PinInput as InputOTPPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
value = $bindable(""),
|
||||||
|
...restProps
|
||||||
|
}: InputOTPPrimitive.RootProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<InputOTPPrimitive.Root
|
||||||
|
bind:ref
|
||||||
|
bind:value
|
||||||
|
data-slot="input-otp"
|
||||||
|
class={cn(
|
||||||
|
"flex items-center gap-2 has-disabled:opacity-50 [&_input]:disabled:cursor-not-allowed",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
<Field>
|
<Field>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<FieldLabel for="password-{id}">Contraseña</FieldLabel>
|
<FieldLabel for="password-{id}">Contraseña</FieldLabel>
|
||||||
<a href="##" class="ml-auto inline-block text-sm underline">
|
<a href="/password-reset" class="ml-auto inline-block text-sm underline">
|
||||||
Te Olvidaste la contraseña?
|
Te Olvidaste la contraseña?
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import BreadcrumbItem from '@/components/ui/breadcrumb/breadcrumb-item.svelte';
|
||||||
|
import BreadcrumbList from '@/components/ui/breadcrumb/breadcrumb-list.svelte';
|
||||||
|
import BreadcrumbSeparator from '@/components/ui/breadcrumb/breadcrumb-separator.svelte';
|
||||||
|
import Breadcrumb from '@/components/ui/breadcrumb/breadcrumb.svelte';
|
||||||
|
|
||||||
|
import Pasos from './Pasos.svelte';
|
||||||
|
import Card from '@/components/ui/card/card.svelte';
|
||||||
|
import CardContent from '@/components/ui/card/card-content.svelte';
|
||||||
|
import { slide } from 'svelte/transition';
|
||||||
|
import IngresarEmail from './IngresarEmail.svelte';
|
||||||
|
import Otp from './Otp.svelte';
|
||||||
|
import NuevaPass from './NuevaPass.svelte';
|
||||||
|
|
||||||
|
let estado: 'email' | 'otp' | 'nuevapass' = $state('email');
|
||||||
|
let email: string = $state('');
|
||||||
|
let otp: string = $state('');
|
||||||
|
</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">
|
||||||
|
<Pasos {estado} />
|
||||||
|
<div class="mt-6">
|
||||||
|
{#if estado === 'email'}
|
||||||
|
<IngresarEmail bind:estado bind:email />
|
||||||
|
{/if}
|
||||||
|
{#if estado === 'otp'}
|
||||||
|
<Otp bind:estado {email} bind:otp />
|
||||||
|
{/if}
|
||||||
|
{#if estado === 'nuevapass'}
|
||||||
|
<NuevaPass {otp} {email} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import CardError from '@/components/CardError.svelte';
|
||||||
|
import Button from '@/components/ui/button/button.svelte';
|
||||||
|
import CardContent from '@/components/ui/card/card-content.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 Spinner from '@/components/ui/spinner/spinner.svelte';
|
||||||
|
import { checkEmail } from '@/hooks/checkEmail';
|
||||||
|
import { apiBase } from '@/stores/url';
|
||||||
|
import Check from '@lucide/svelte/icons/check';
|
||||||
|
import Cross from '@lucide/svelte/icons/x';
|
||||||
|
import { slide } from 'svelte/transition';
|
||||||
|
|
||||||
|
let { estado = $bindable(), email = $bindable() } = $props();
|
||||||
|
|
||||||
|
let checkeado = $state<Boolean | null>(null);
|
||||||
|
let esEmailExistente = $state<boolean>(false);
|
||||||
|
let mensajeError = $state('');
|
||||||
|
let lastemail: string;
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (email == '' || !email.includes('@')) {
|
||||||
|
checkeado = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
||||||
|
(async () => {
|
||||||
|
if (timeoutId) clearTimeout(timeoutId);
|
||||||
|
timeoutId = setTimeout(async () => {
|
||||||
|
checkeado = true;
|
||||||
|
await checkEmaill();
|
||||||
|
checkeado = false;
|
||||||
|
}, 1000);
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function checkEmaill() {
|
||||||
|
try {
|
||||||
|
if (lastemail == email) return;
|
||||||
|
lastemail = email;
|
||||||
|
esEmailExistente = !(await checkEmail(email));
|
||||||
|
} catch {
|
||||||
|
esEmailExistente = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if mensajeError}
|
||||||
|
<Dialog open={!!mensajeError} onOpenChange={() => (mensajeError = '')}>
|
||||||
|
<DialogContent>
|
||||||
|
<CardError {mensajeError} />
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
{/if}
|
||||||
|
<div transition:slide>
|
||||||
|
<Card>
|
||||||
|
<CardContent>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<h2 class="flex items-center justify-between text-xl font-semibold">
|
||||||
|
Ingresa tu correo electrónico
|
||||||
|
{#if checkeado == null}
|
||||||
|
<div hidden></div>
|
||||||
|
{:else if checkeado == true}
|
||||||
|
<Spinner></Spinner>
|
||||||
|
{:else if esEmailExistente}
|
||||||
|
<Check class="text-green-500" />
|
||||||
|
{:else}
|
||||||
|
<Cross class="text-red-500" />
|
||||||
|
{/if}
|
||||||
|
</h2>
|
||||||
|
<form
|
||||||
|
onsubmit={async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<Input type="email" placeholder="correo@ejemplo.com" bind:value={email} />
|
||||||
|
<Button type="submit" disabled={!esEmailExistente}>Enviar código</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<script>
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import CardError from '@/components/CardError.svelte';
|
||||||
|
import Button from '@/components/ui/button/button.svelte';
|
||||||
|
import CardContent from '@/components/ui/card/card-content.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 Label from '@/components/ui/label/label.svelte';
|
||||||
|
import { apiBase } from '@/stores/url';
|
||||||
|
import { sesionStore } from '@/stores/usuario';
|
||||||
|
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>
|
||||||
|
|
||||||
|
{#if mensajeError}
|
||||||
|
<Dialog open={!!mensajeError} onOpenChange={() => (mensajeError = '')}>
|
||||||
|
<DialogContent>
|
||||||
|
<CardError {mensajeError} />
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div transition:slide>
|
||||||
|
<Card>
|
||||||
|
<CardContent>
|
||||||
|
<h2 class="mb-4 text-2xl font-bold">Crear una Nueva Contraseña</h2>
|
||||||
|
<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>
|
||||||
|
<Label for="password" class="mb-1 block text-sm font-medium">Contraseña</Label>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
class="w-full px-3 py-2"
|
||||||
|
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>
|
||||||
|
<Label for="confirmPassword" class="mb-1 block text-sm font-medium"
|
||||||
|
>Repetir Contraseña</Label
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
id="confirmPassword"
|
||||||
|
class="w-full px-3 py-2"
|
||||||
|
placeholder="Repite tu nueva contraseña"
|
||||||
|
bind:value={pass2}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button disabled={!coinsiden} type="submit">Establecer Contraseña</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
<script>
|
||||||
|
import CardError from '@/components/CardError.svelte';
|
||||||
|
import Button from '@/components/ui/button/button.svelte';
|
||||||
|
import CardContent from '@/components/ui/card/card-content.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 InputOtpSlot from '@/components/ui/input-otp/input-otp-slot.svelte';
|
||||||
|
import InputOtp from '@/components/ui/input-otp/input-otp.svelte';
|
||||||
|
import { apiBase } from '@/stores/url';
|
||||||
|
import { slide } from 'svelte/transition';
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
{#if mensajeError}
|
||||||
|
<Dialog open={!!mensajeError} onOpenChange={() => (mensajeError = '')}>
|
||||||
|
<DialogContent>
|
||||||
|
<CardError {mensajeError} />
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div transition:slide>
|
||||||
|
<Card>
|
||||||
|
<CardContent>
|
||||||
|
<div class="space-y-6 py-4">
|
||||||
|
<h3 class="text-xl font-semibold">Verificación de correo</h3>
|
||||||
|
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
Hemos enviado un código de verificación a tu correo electrónico.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<InputOtp maxlength={6} bind:value={otp}>
|
||||||
|
{#snippet children({ cells })}
|
||||||
|
<InputOtpGroup>
|
||||||
|
{#each cells as cell}
|
||||||
|
<InputOtpSlot class="p-3!" {cell}></InputOtpSlot>
|
||||||
|
{/each}
|
||||||
|
</InputOtpGroup>
|
||||||
|
{/snippet}
|
||||||
|
</InputOtp>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<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')}>
|
||||||
|
Reenviar código de verificación
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<script>
|
||||||
|
import BreadcrumbItem from '@/components/ui/breadcrumb/breadcrumb-item.svelte';
|
||||||
|
import BreadcrumbList from '@/components/ui/breadcrumb/breadcrumb-list.svelte';
|
||||||
|
import BreadcrumbSeparator from '@/components/ui/breadcrumb/breadcrumb-separator.svelte';
|
||||||
|
import Breadcrumb from '@/components/ui/breadcrumb/breadcrumb.svelte';
|
||||||
|
|
||||||
|
let { estado } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex w-full justify-center">
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbList>
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<p class={`select-none ${estado === 'email' ? 'font-bold text-white' : ''}`}>
|
||||||
|
Ingrese Email
|
||||||
|
</p>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
<BreadcrumbSeparator />
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<p class={`select-none ${estado === 'otp' ? 'font-bold text-white' : ''}`}>Ingresar OTP</p>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
<BreadcrumbSeparator />
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<p class={`select-none ${estado === 'nuevapass' ? 'font-bold text-white' : ''}`}>
|
||||||
|
Nueva Contraseña
|
||||||
|
</p>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
</BreadcrumbList>
|
||||||
|
</Breadcrumb>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user