adds password reset
This commit is contained in:
26
web/src/routes/+error.svelte
Normal file
26
web/src/routes/+error.svelte
Normal file
@@ -0,0 +1,26 @@
|
||||
<script>
|
||||
import { page } from "$app/stores";
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex flex-col gap-y-4 items-center justify-center relative"
|
||||
style="height: calc(100dvh - 120px); font-size: 4rem"
|
||||
>
|
||||
<img
|
||||
class="absolute top-1/2 left-1/2 aspect-square rounded-xl object-cover"
|
||||
width="500"
|
||||
src="/imgs/error.webp"
|
||||
alt="Default error img"
|
||||
/>
|
||||
<div
|
||||
class="z-10 rounded-full p-8 bg-background flex flex-col gap-y-4 items-center"
|
||||
style="max-width: 400px"
|
||||
>
|
||||
<h1 class="font-bold" style="line-height: 4rem;">
|
||||
{$page.status}
|
||||
</h1>
|
||||
<p class="text-lg text-center">
|
||||
{$page.error?.message}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
20
web/src/routes/api/v1/auth/confirm-reset/+server.ts
Normal file
20
web/src/routes/api/v1/auth/confirm-reset/+server.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
try {
|
||||
let data;
|
||||
try {
|
||||
data = await event.request.json()
|
||||
} catch (e) {
|
||||
throw new ClientResponseError({ status: 400, response: { message: "Invalid json" } })
|
||||
}
|
||||
|
||||
const r = await pb.collection('users').confirmPasswordReset(data.token, data.password, data.passwordConfirm);
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
}
|
||||
|
||||
}
|
||||
20
web/src/routes/api/v1/auth/reset/+server.ts
Normal file
20
web/src/routes/api/v1/auth/reset/+server.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
try {
|
||||
let data;
|
||||
try {
|
||||
data = await event.request.json()
|
||||
} catch (e) {
|
||||
throw new ClientResponseError({ status: 400, response: { message: "Invalid json" } })
|
||||
}
|
||||
|
||||
const r = await pb.collection('users').requestPasswordReset(data.email);
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { error, type ServerLoad } from "@sveltejs/kit";
|
||||
|
||||
export const load: ServerLoad = async ({ params, locals, url, fetch }) => {
|
||||
if(!params.token) {
|
||||
error(400, "Invalid token")
|
||||
}
|
||||
};
|
||||
98
web/src/routes/auth/confirm-reset/[token]/+page.svelte
Normal file
98
web/src/routes/auth/confirm-reset/[token]/+page.svelte
Normal file
@@ -0,0 +1,98 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/stores";
|
||||
import Button from "$lib/components/base/button.svelte";
|
||||
import TextField from "$lib/components/base/text_field.svelte";
|
||||
import LogoTextTwoLineDark from "$lib/components/logo/logo_text_two_line_dark.svelte";
|
||||
import LogoTextTwoLineLight from "$lib/components/logo/logo_text_two_line_light.svelte";
|
||||
import type { User } from "$lib/models/user";
|
||||
import { theme } from "$lib/stores/theme_store";
|
||||
import { show_toast } from "$lib/stores/toast_store";
|
||||
import {
|
||||
users_confirm_reset,
|
||||
users_reset_password,
|
||||
} from "$lib/stores/user_store";
|
||||
import { createForm } from "$lib/vendor/svelte-form-lib";
|
||||
import { _ } from "svelte-i18n";
|
||||
import { object, ref, string } from "yup";
|
||||
|
||||
let loading: boolean = false;
|
||||
|
||||
const { form, errors, handleChange, handleSubmit } = createForm({
|
||||
initialValues: {
|
||||
password: "",
|
||||
passwordConfirm: "",
|
||||
},
|
||||
validationSchema: object({
|
||||
password: string().required($_("required")),
|
||||
passwordConfirm: string()
|
||||
.required($_("required"))
|
||||
.oneOf([ref("password"), ""], $_('passwords-must-match')),
|
||||
}),
|
||||
onSubmit: async (form) => {
|
||||
loading = true;
|
||||
try {
|
||||
await users_confirm_reset({
|
||||
...form,
|
||||
token: $page.params.token,
|
||||
});
|
||||
goto("/login");
|
||||
show_toast({
|
||||
icon: "check",
|
||||
type: "success",
|
||||
text: $_("new-password-success"),
|
||||
});
|
||||
} catch (e) {
|
||||
show_toast({
|
||||
icon: "close",
|
||||
type: "error",
|
||||
text: $_("new-password-error"),
|
||||
});
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$_("login")} | wanderer</title>
|
||||
</svelte:head>
|
||||
<main class="flex justify-center">
|
||||
<form
|
||||
class="login-panel max-w-md border border-input-border rounded-xl p-8 flex flex-col justify-center items-center gap-4 w-[28rem] mt-8"
|
||||
on:submit={handleSubmit}
|
||||
>
|
||||
{#if $theme == "light"}
|
||||
<LogoTextTwoLineDark></LogoTextTwoLineDark>
|
||||
{:else}
|
||||
<LogoTextTwoLineLight></LogoTextTwoLineLight>
|
||||
{/if}
|
||||
<h4 class="text-xl font-semibold">{$_("new-password-text")}</h4>
|
||||
<div class="space-y-6 w-80">
|
||||
<TextField
|
||||
name="password"
|
||||
label={$_("password")}
|
||||
type="password"
|
||||
bind:value={$form.password}
|
||||
on:change={handleChange}
|
||||
error={$errors.password}
|
||||
></TextField>
|
||||
<TextField
|
||||
name="password"
|
||||
label={$_("password-confirm")}
|
||||
type="password"
|
||||
bind:value={$form.passwordConfirm}
|
||||
on:change={handleChange}
|
||||
error={$errors.passwordConfirm}
|
||||
></TextField>
|
||||
|
||||
<Button
|
||||
primary={true}
|
||||
extraClasses={"min-w-full"}
|
||||
type="submit"
|
||||
{loading}>{$_("reset-password")}</Button
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
83
web/src/routes/auth/reset/+page.svelte
Normal file
83
web/src/routes/auth/reset/+page.svelte
Normal file
@@ -0,0 +1,83 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import Button from "$lib/components/base/button.svelte";
|
||||
import TextField from "$lib/components/base/text_field.svelte";
|
||||
import LogoTextTwoLineDark from "$lib/components/logo/logo_text_two_line_dark.svelte";
|
||||
import LogoTextTwoLineLight from "$lib/components/logo/logo_text_two_line_light.svelte";
|
||||
import type { User } from "$lib/models/user";
|
||||
import { theme } from "$lib/stores/theme_store";
|
||||
import { show_toast } from "$lib/stores/toast_store";
|
||||
import { users_reset_password } from "$lib/stores/user_store";
|
||||
import { createForm } from "$lib/vendor/svelte-form-lib";
|
||||
import { _ } from "svelte-i18n";
|
||||
import { object, string } from "yup";
|
||||
|
||||
let loading: boolean = false;
|
||||
|
||||
const { form, errors, handleChange, handleSubmit } = createForm({
|
||||
initialValues: {
|
||||
email: "",
|
||||
},
|
||||
validationSchema: object<User>({
|
||||
email: string().required($_("required")),
|
||||
}),
|
||||
onSubmit: async (email) => {
|
||||
loading = true;
|
||||
try {
|
||||
await users_reset_password(email);
|
||||
goto("/");
|
||||
show_toast({
|
||||
icon: "check",
|
||||
type: "success",
|
||||
text: $_("password-reset-sent"),
|
||||
});
|
||||
} catch (e) {
|
||||
show_toast({
|
||||
icon: "close",
|
||||
type: "error",
|
||||
text: $_("error-during-password-reset"),
|
||||
});
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$_("login")} | wanderer</title>
|
||||
</svelte:head>
|
||||
<main class="flex justify-center">
|
||||
<form
|
||||
class="login-panel max-w-md border border-input-border rounded-xl p-8 flex flex-col justify-center items-center gap-4 w-[28rem] mt-8"
|
||||
on:submit={handleSubmit}
|
||||
>
|
||||
{#if $theme == "light"}
|
||||
<LogoTextTwoLineDark></LogoTextTwoLineDark>
|
||||
{:else}
|
||||
<LogoTextTwoLineLight></LogoTextTwoLineLight>
|
||||
{/if}
|
||||
<h4 class="text-xl font-semibold">{$_("forgot-your-password")}</h4>
|
||||
<p class="text-gray-500">{$_('password-reset-text')}</p>
|
||||
<div class="space-y-6 w-80">
|
||||
<TextField
|
||||
name="email"
|
||||
label={$_("email")}
|
||||
bind:value={$form.email}
|
||||
on:change={handleChange}
|
||||
error={$errors.email}
|
||||
></TextField>
|
||||
<div class="grid grid-cols-2 gap-x-4">
|
||||
<a class="btn-secondary text-center" href="/login"
|
||||
>{$_("back-to-login")}</a
|
||||
>
|
||||
<Button
|
||||
primary={true}
|
||||
extraClasses={"min-w-full"}
|
||||
type="submit"
|
||||
{loading}>{$_("reset-password")}</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
@@ -97,14 +97,20 @@
|
||||
on:change={handleChange}
|
||||
error={$errors.username}
|
||||
></TextField>
|
||||
<TextField
|
||||
name="password"
|
||||
label={$_("password")}
|
||||
type="password"
|
||||
bind:value={$form.password}
|
||||
on:change={handleChange}
|
||||
error={$errors.password}
|
||||
></TextField>
|
||||
<div class="flex flex-col">
|
||||
<TextField
|
||||
name="password"
|
||||
label={$_("password")}
|
||||
type="password"
|
||||
bind:value={$form.password}
|
||||
on:change={handleChange}
|
||||
error={$errors.password}
|
||||
></TextField>
|
||||
<a
|
||||
class="text-xs underline decoration-dashed float-end mt-1 self-end"
|
||||
href="/auth/reset">{$_("forgot-your-password")}</a
|
||||
>
|
||||
</div>
|
||||
<Button
|
||||
primary={true}
|
||||
extraClasses={"min-w-full"}
|
||||
|
||||
Reference in New Issue
Block a user