mirror of
https://github.com/versia-pub/frontend.git
synced 2026-03-13 03:29:16 +01:00
feat: 👽 Support Versia Server 0.9 login system
Some checks failed
Some checks failed
This commit is contained in:
parent
cc95f043bd
commit
dc32f3b3ea
21 changed files with 221 additions and 567 deletions
|
|
@ -1,39 +0,0 @@
|
|||
<template>
|
||||
<Alert layout="button">
|
||||
<LogIn />
|
||||
<AlertTitle>{{ m.sunny_quick_lionfish_flip() }}</AlertTitle>
|
||||
<AlertDescription>
|
||||
{{ m.brave_known_pelican_drip() }}
|
||||
</AlertDescription>
|
||||
<Button
|
||||
variant="secondary"
|
||||
class="w-full"
|
||||
@click="signInAction"
|
||||
>
|
||||
{{ m.fuzzy_sea_moth_absorb() }}
|
||||
</Button>
|
||||
</Alert>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { LogIn } from "lucide-vue-next";
|
||||
import { toast } from "vue-sonner";
|
||||
import { Alert, AlertDescription, AlertTitle } from "~/components/ui/alert";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import * as m from "~~/paraglide/messages.js";
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const signInAction = async () => {
|
||||
const instance = await askForInstance();
|
||||
|
||||
const id = toast.loading(m.level_due_ox_greet());
|
||||
|
||||
try {
|
||||
await authStore.startSignIn(instance);
|
||||
} catch (e) {
|
||||
toast.dismiss(id);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
|
@ -1,23 +1,27 @@
|
|||
<template>
|
||||
<Alert layout="button">
|
||||
<TriangleAlert />
|
||||
<AlertTitle>{{ contentWarning || m.sour_seemly_bird_hike() }}</AlertTitle>
|
||||
<Button @click="blurred = !blurred" variant="outline" size="sm">{{ blurred ? m.bald_direct_turtle_win() :
|
||||
m.known_flaky_cockroach_dash() }}</Button>
|
||||
</Alert>
|
||||
<div class="flex flex-col gap-1">
|
||||
<p class="text-sm leading-6 wrap-anywhere">
|
||||
{{ contentWarning || m.sour_seemly_bird_hike() }}
|
||||
</p>
|
||||
<Button @click="hidden = !hidden" variant="outline" size="sm" class="col-span-2">
|
||||
{{ hidden ? m.bald_direct_turtle_win() :
|
||||
m.known_flaky_cockroach_dash() }} {{ characterCount > 0 ? ` (${characterCount} characters` : "" }}{{
|
||||
attachmentCount > 0 ? `${characterCount > 0 ? " · " : " ("}${attachmentCount} file(s)` : "" }}{{ (characterCount > 0 || attachmentCount > 0) ? ")" : "" }}
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { TriangleAlert } from "lucide-vue-next";
|
||||
import * as m from "~~/paraglide/messages.js";
|
||||
import { Alert, AlertDescription, AlertTitle } from "../ui/alert";
|
||||
import { Button } from "../ui/button";
|
||||
|
||||
const { contentWarning } = defineProps<{
|
||||
const { contentWarning, characterCount, attachmentCount } = defineProps<{
|
||||
contentWarning?: string;
|
||||
characterCount: number;
|
||||
attachmentCount: number;
|
||||
}>();
|
||||
|
||||
const blurred = defineModel<boolean>({
|
||||
const hidden = defineModel<boolean>({
|
||||
default: true,
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
<template>
|
||||
<ContentWarning v-if="(sensitive || contentWarning) && preferences.show_content_warning" :content-warning="contentWarning" v-model="blurred" />
|
||||
<ContentWarning v-if="(sensitive || contentWarning) && preferences.show_content_warning" :content-warning="contentWarning" :character-count="characterCount ?? 0" :attachment-count="attachments.length" v-model="hidden" />
|
||||
|
||||
<OverflowGuard v-if="content" :character-count="characterCount" :class="(blurred && preferences.show_content_warning) && 'blur-md'">
|
||||
<OverflowGuard v-if="content" :character-count="characterCount" :class="(hidden && preferences.show_content_warning) && 'hidden'">
|
||||
<Prose v-html="content" v-render-emojis="emojis"></Prose>
|
||||
</OverflowGuard>
|
||||
|
||||
<Attachments v-if="attachments.length > 0" :attachments="attachments" :class="(blurred && preferences.show_content_warning) && 'blur-xl'" />
|
||||
<Attachments v-if="attachments.length > 0" :attachments="attachments" :class="(hidden && preferences.show_content_warning) && 'hidden'" />
|
||||
|
||||
<div v-if="quote" class="mt-4 rounded border overflow-hidden">
|
||||
<Note :note="quote" :hide-actions="true" :small-layout="true" />
|
||||
|
|
@ -31,7 +31,7 @@ const { content, plainContent, sensitive, contentWarning } = defineProps<{
|
|||
contentWarning?: string;
|
||||
}>();
|
||||
|
||||
const blurred = ref(sensitive || !!contentWarning);
|
||||
const hidden = ref(sensitive || !!contentWarning);
|
||||
|
||||
const characterCount = plainContent?.length;
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -16,13 +16,13 @@
|
|||
:visibility="noteToUse.visibility"
|
||||
:created-at="new Date(noteToUse.created_at)"
|
||||
:small-layout="smallLayout"
|
||||
class="z-[1]"
|
||||
class="z-1"
|
||||
/>
|
||||
<div
|
||||
v-if="topAvatarBar"
|
||||
:class="
|
||||
cn(
|
||||
'shrink-0 bg-border w-0.5 absolute top-0 h-7 left-[3rem]'
|
||||
'shrink-0 bg-border w-0.5 absolute top-0 h-7 left-12'
|
||||
)
|
||||
"
|
||||
></div>
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
v-if="bottomAvatarBar"
|
||||
:class="
|
||||
cn(
|
||||
'shrink-0 bg-border w-0.5 absolute bottom-0 h-[calc(100%-1.5rem)] left-[3rem]'
|
||||
'shrink-0 bg-border w-0.5 absolute bottom-0 h-[calc(100%-1.5rem)] left-12'
|
||||
)
|
||||
"
|
||||
></div>
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
<!-- Simply offset by the size of avatar + 0.75rem (the gap) -->
|
||||
<CardContent
|
||||
:class="
|
||||
['space-y-4', contentUnderUsername && (smallLayout ? 'ml-11' : 'ml-[4.25rem]')]
|
||||
['space-y-4', contentUnderUsername && (smallLayout ? 'ml-11' : 'ml-17')]
|
||||
"
|
||||
>
|
||||
<Content
|
||||
|
|
|
|||
|
|
@ -1,171 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { toTypedSchema } from "@vee-validate/zod";
|
||||
import type { Instance } from "@versia/client/schemas";
|
||||
import { Loader } from "lucide-vue-next";
|
||||
import { useForm } from "vee-validate";
|
||||
import * as z from "zod";
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Input } from "~/components/ui/input";
|
||||
import * as m from "~~/paraglide/messages.js";
|
||||
|
||||
const { instance } = defineProps<{
|
||||
instance: z.infer<typeof Instance>;
|
||||
}>();
|
||||
|
||||
const isLoading = ref(false);
|
||||
const ssoConfig = computed(() => instance.sso);
|
||||
|
||||
const formSchema = toTypedSchema(
|
||||
z.object({
|
||||
identifier: z
|
||||
.string()
|
||||
.min(3, {
|
||||
message: m.aware_house_dolphin_win(),
|
||||
})
|
||||
.or(
|
||||
z.string().email({
|
||||
message: m.weary_fresh_dragonfly_bless(),
|
||||
}),
|
||||
),
|
||||
password: z.string().min(3, {
|
||||
message: m.aware_house_dolphin_win(),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
const form = useForm({
|
||||
validationSchema: formSchema,
|
||||
});
|
||||
|
||||
const redirectUrl = new URL("/api/auth/login", `https://${instance.domain}`);
|
||||
|
||||
const params = useUrlSearchParams();
|
||||
|
||||
for (const name of [
|
||||
"redirect_uri",
|
||||
"response_type",
|
||||
"client_id",
|
||||
"scope",
|
||||
"state",
|
||||
]) {
|
||||
if (params[name]) {
|
||||
redirectUrl.searchParams.set(name, params[name] as string);
|
||||
}
|
||||
}
|
||||
|
||||
const issuerRedirectUrl = (issuerId: string) => {
|
||||
const url = new URL("/oauth/sso", useRequestURL().origin);
|
||||
|
||||
for (const name of [
|
||||
"redirect_uri",
|
||||
"response_type",
|
||||
"client_id",
|
||||
"scope",
|
||||
"state",
|
||||
]) {
|
||||
if (params[name]) {
|
||||
url.searchParams.set(name, params[name] as string);
|
||||
}
|
||||
}
|
||||
|
||||
url.searchParams.set("issuer", issuerId);
|
||||
return url.toString();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid gap-6">
|
||||
<form
|
||||
@submit="form.submitForm"
|
||||
method="post"
|
||||
:action="redirectUrl.toString()"
|
||||
>
|
||||
<div class="grid gap-6">
|
||||
<FormField v-slot="{ componentField }" name="identifier">
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{{ m.fluffy_soft_wolf_cook() }}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="petergriffin"
|
||||
type="text"
|
||||
auto-capitalize="none"
|
||||
auto-complete="idenfifier"
|
||||
auto-correct="off"
|
||||
:disabled="isLoading"
|
||||
v-bind="componentField"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<FormField v-slot="{ componentField }" name="password">
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{{ m.livid_bright_wallaby_quiz() }}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="hunter2"
|
||||
type="password"
|
||||
auto-capitalize="none"
|
||||
auto-complete="password"
|
||||
auto-correct="off"
|
||||
:disabled="isLoading"
|
||||
v-bind="componentField"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<Button :disabled="isLoading" type="submit">
|
||||
<Loader v-if="isLoading" class="mr-2 size-4 animate-spin" />
|
||||
{{ m.fuzzy_sea_moth_absorb() }}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
<div
|
||||
v-if="ssoConfig && ssoConfig.providers.length > 0"
|
||||
class="relative"
|
||||
>
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<span class="w-full border-t" />
|
||||
</div>
|
||||
<div class="relative flex justify-center text-xs uppercase">
|
||||
<span class="bg-background px-2 text-muted-foreground">
|
||||
{{ m.tidy_tidy_cow_cut() }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="ssoConfig && ssoConfig.providers.length > 0"
|
||||
class="flex flex-col gap-2"
|
||||
>
|
||||
<Button
|
||||
as="a"
|
||||
:href="issuerRedirectUrl(provider.id)"
|
||||
variant="outline"
|
||||
type="button"
|
||||
:disabled="isLoading"
|
||||
v-for="provider of ssoConfig.providers"
|
||||
>
|
||||
<Loader v-if="isLoading" class="mr-2 animate-spin" />
|
||||
<img
|
||||
crossorigin="anonymous"
|
||||
:src="provider.icon"
|
||||
:alt="`${provider.name}'s logo`"
|
||||
class="size-4 mr-2"
|
||||
/>
|
||||
{{ provider.name }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
<template>
|
||||
<form class="grid gap-6" @submit="save">
|
||||
<Transition name="slide-up">
|
||||
<Alert v-if="dirty" layout="button" class="absolute bottom-2 z-10 inset-x-2 w-[calc(100%-1rem)]">
|
||||
<Alert v-if="dirty" class="absolute bottom-2 z-10 inset-x-2 w-[calc(100%-1rem)] grid-cols-[calc(var(--spacing)*4)_1fr_auto]!">
|
||||
<SaveOff class="size-4" />
|
||||
<AlertTitle>Unsaved changes</AlertTitle>
|
||||
<Button variant="secondary" class="w-full row-span-2" type="button" :disabled="submitting">Apply</Button>
|
||||
<AlertDescription>
|
||||
Click "apply" to save your changes.
|
||||
</AlertDescription>
|
||||
<Button variant="secondary" class="w-full" typ="submit" :disabled="submitting">Apply</Button>
|
||||
</Alert>
|
||||
</Transition>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,14 +6,13 @@ import { type AlertVariants, alertVariants } from ".";
|
|||
const props = defineProps<{
|
||||
class?: HTMLAttributes["class"];
|
||||
variant?: AlertVariants["variant"];
|
||||
layout?: AlertVariants["layout"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
data-slot="alert"
|
||||
:class="cn(alertVariants({ variant, layout }), props.class)"
|
||||
:class="cn(alertVariants({ variant }), props.class)"
|
||||
role="alert"
|
||||
>
|
||||
<slot />
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const props = defineProps<{
|
|||
<template>
|
||||
<div
|
||||
data-slot="alert-description"
|
||||
:class="cn('text-muted-foreground text-sm [&_p]:leading-relaxed', props.class)"
|
||||
:class="cn('text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const props = defineProps<{
|
|||
<template>
|
||||
<div
|
||||
data-slot="alert-title"
|
||||
:class="cn('line-clamp-1 min-h-4 font-medium tracking-tight', props.class)"
|
||||
:class="cn('col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ export { default as AlertDescription } from "./AlertDescription.vue";
|
|||
export { default as AlertTitle } from "./AlertTitle.vue";
|
||||
|
||||
export const alertVariants = cva(
|
||||
"relative w-full rounded-lg border px-4 py-3 grid text-sm [&>svg]:size-4 [&>svg]:text-current",
|
||||
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
|
|
@ -13,15 +13,9 @@ export const alertVariants = cva(
|
|||
destructive:
|
||||
"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
|
||||
},
|
||||
layout: {
|
||||
default:
|
||||
"has-[>svg]:grid-cols-[1fr_auto] grid-rows-2 gap-x-3 gap-y-1 items-start",
|
||||
button: "grid-cols-[auto_1fr_auto] items-center gap-x-3 gap-y-0.5 *:data-[slot=alert-description]:col-start-2 *:data-[slot=alert-description]:row-start-2 has-[>[data-slot=alert-description]]:[&>button]:row-span-2",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
layout: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
<template>
|
||||
<SidebarProvider>
|
||||
<AppSidebar>
|
||||
<slot v-if="!route.meta.requiresAuth || authStore.isSignedIn" />
|
||||
<div class="mx-auto max-w-4xl p-4" v-else>
|
||||
<AuthRequired />
|
||||
</div>
|
||||
<slot />
|
||||
</AppSidebar>
|
||||
</SidebarProvider>
|
||||
<MobileNavbar v-if="authStore.isSignedIn" />
|
||||
|
|
@ -14,7 +11,6 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import ComposerDialog from "~/components/composer/dialog.vue";
|
||||
import AuthRequired from "~/components/errors/AuthRequired.vue";
|
||||
import MobileNavbar from "~/components/navigation/mobile-navbar.vue";
|
||||
import Preferences from "~/components/preferences/index.vue";
|
||||
import AppSidebar from "~/components/sidebars/sidebar.vue";
|
||||
|
|
@ -32,6 +28,17 @@ const notUsingInput = computed(
|
|||
);
|
||||
const route = useRoute();
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
async () => {
|
||||
console.log(route.meta.requiresAuth && !authStore.isSignedIn);
|
||||
if (route.meta.requiresAuth && !authStore.isSignedIn) {
|
||||
window.location.href = "/";
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch([n, notUsingInput, d], async () => {
|
||||
if (n?.value && notUsingInput.value) {
|
||||
// Wait 50ms
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
import { Client } from "@versia/client";
|
||||
import { AlertCircle, Loader } from "lucide-vue-next";
|
||||
import { NuxtLink } from "#components";
|
||||
import UserAuthForm from "~/components/oauth/login.vue";
|
||||
import { Alert, AlertDescription, AlertTitle } from "~/components/ui/alert";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import * as m from "~~/paraglide/messages.js";
|
||||
|
|
@ -11,8 +10,8 @@ useHead({
|
|||
title: m.fuzzy_sea_moth_absorb(),
|
||||
});
|
||||
|
||||
const baseUrl = useRequestURL();
|
||||
const client = computed(() => new Client(new URL(baseUrl)));
|
||||
const baseUrl = new URL("https://versia.localhost"); //useRequestURL();
|
||||
const client = computed(() => new Client(baseUrl));
|
||||
const instance = useInstanceFromClient(client);
|
||||
const {
|
||||
error,
|
||||
|
|
@ -22,8 +21,13 @@ const {
|
|||
client_id,
|
||||
scope,
|
||||
} = useUrlSearchParams();
|
||||
|
||||
const hasValidUrlSearchParams =
|
||||
redirect_uri && response_type && client_id && scope;
|
||||
|
||||
const isLoading = ref(false);
|
||||
const getProviderUrl = (providerId: string) =>
|
||||
new URL(`/oauth/sso/${providerId}`, baseUrl).toString();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -45,7 +49,7 @@ const hasValidUrlSearchParams =
|
|||
}"
|
||||
>
|
||||
<div
|
||||
class="absolute top-0 left-0 w-full h-72 bg-gradient-to-t from-transparent to-black/70"
|
||||
class="absolute top-0 left-0 w-full h-72 bg-linear-to-t from-transparent to-black/70"
|
||||
/>
|
||||
<div class="relative z-20 flex items-center text-lg font-medium">
|
||||
<img
|
||||
|
|
@ -59,18 +63,6 @@ const hasValidUrlSearchParams =
|
|||
/>
|
||||
{{ instance?.title }}
|
||||
</div>
|
||||
<!-- <div class="relative z-20 mt-auto">
|
||||
<blockquote class="space-y-2">
|
||||
<p class="text-lg">
|
||||
“This library has saved me countless hours of work and
|
||||
helped me deliver stunning designs to my clients faster than
|
||||
ever before.”
|
||||
</p>
|
||||
<footer class="text-sm">
|
||||
Sofia Davis
|
||||
</footer>
|
||||
</blockquote>
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="lg:p-8 w-full max-w-xl">
|
||||
<div
|
||||
|
|
@ -96,9 +88,45 @@ const hasValidUrlSearchParams =
|
|||
"
|
||||
></p>
|
||||
</div>
|
||||
<template v-if="instance && hasValidUrlSearchParams">
|
||||
<UserAuthForm :instance="instance" />
|
||||
</template>
|
||||
<div v-if="instance && hasValidUrlSearchParams" class="grid gap-6">
|
||||
<div
|
||||
v-if="instance.sso.providers.length > 0"
|
||||
class="flex flex-col gap-2"
|
||||
>
|
||||
<form v-for="provider of instance.sso.providers" :key="provider.id" method="POST" :action="getProviderUrl(provider.id)">
|
||||
<input type="hidden" name="redirect_uri" :value="redirect_uri" />
|
||||
<input type="hidden" name="client_id" :value="client_id" />
|
||||
<input v-for="(scopePart, index) of (scope as string).split(' ')" type="hidden" :name="`scope[${index}]`" :value="scopePart" />
|
||||
<Button
|
||||
variant="outline"
|
||||
type="submit"
|
||||
:disabled="isLoading"
|
||||
class="w-full"
|
||||
>
|
||||
<Loader v-if="isLoading" class="mr-2 animate-spin" />
|
||||
<img
|
||||
crossorigin="anonymous"
|
||||
:src="provider.icon"
|
||||
:alt="`${provider.name}'s logo`"
|
||||
class="size-4 mr-2"
|
||||
/>
|
||||
{{ provider.name }}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<Alert v-else variant="destructive" class="mb-4">
|
||||
<AlertCircle class="size-4" />
|
||||
<AlertTitle>
|
||||
No SSO providers are configured.
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
<p>
|
||||
Please ask the administrator of
|
||||
{{ instance.domain }} to set up SSO providers.
|
||||
</p>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="hasValidUrlSearchParams"
|
||||
class="p-4 flex items-center justify-center h-48"
|
||||
|
|
|
|||
|
|
@ -1,187 +0,0 @@
|
|||
<template>
|
||||
<Card v-if="params.success" class="w-full max-w-md *:w-full">
|
||||
<CardHeader>
|
||||
<CardTitle>{{ m.late_mean_capybara_fade() }}</CardTitle>
|
||||
<CardDescription>
|
||||
{{ m.brave_acidic_lobster_fetch() }}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter class="grid">
|
||||
<Button :as="NuxtLink" href="/" variant="default">
|
||||
{{ m.every_tangy_koala_persist() }}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card v-else class="w-full max-w-md">
|
||||
<form
|
||||
method="POST"
|
||||
action="/api/auth/reset"
|
||||
@submit="form.submitForm"
|
||||
>
|
||||
<CardHeader>
|
||||
<Alert
|
||||
v-if="params.login_reset"
|
||||
variant="default"
|
||||
class="mb-4"
|
||||
>
|
||||
<AlertCircle class="size-4" />
|
||||
<AlertTitle>{{
|
||||
m.east_loud_lobster_explore()
|
||||
}}</AlertTitle>
|
||||
<AlertDescription>
|
||||
{{ m.good_plane_gazelle_glow() }}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<Alert
|
||||
v-if="params.error"
|
||||
variant="destructive"
|
||||
class="mb-4"
|
||||
>
|
||||
<AlertCircle class="size-4" />
|
||||
<AlertTitle>{{ params.error }}</AlertTitle>
|
||||
<AlertDescription>
|
||||
{{ params.error_description }}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<CardTitle as="h1">{{
|
||||
m.tired_green_sloth_evoke()
|
||||
}}</CardTitle>
|
||||
<CardDescription>
|
||||
{{ m.solid_slow_platypus_talk() }}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent class="grid gap-6">
|
||||
<input type="hidden" name="token" :value="params.token" />
|
||||
<FormField v-slot="{ componentField }" name="password">
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{{ m.true_male_gadfly_stab() }}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="hunter2"
|
||||
type="password"
|
||||
auto-capitalize="none"
|
||||
auto-correct="off"
|
||||
v-bind="componentField"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="password-confirm"
|
||||
>
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{{ m.awful_cozy_jannes_rise() }}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="hunter2"
|
||||
type="password"
|
||||
auto-capitalize="none"
|
||||
auto-correct="off"
|
||||
v-bind="componentField"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</CardContent>
|
||||
<CardFooter class="grid gap-2 mt-4">
|
||||
<Button variant="default" type="submit">{{
|
||||
m.noisy_round_skate_yell()
|
||||
}}</Button>
|
||||
</CardFooter>
|
||||
</form>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { toTypedSchema } from "@vee-validate/zod";
|
||||
import { AlertCircle } from "lucide-vue-next";
|
||||
import { useForm } from "vee-validate";
|
||||
import { z } from "zod";
|
||||
import { NuxtLink } from "#components";
|
||||
import { Alert, AlertDescription, AlertTitle } from "~/components/ui/alert";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "~/components/ui/card";
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "~/components/ui/form";
|
||||
import { Input } from "~/components/ui/input";
|
||||
import * as m from "~~/paraglide/messages.js";
|
||||
|
||||
useHead({
|
||||
title: m.arable_arable_herring_lead(),
|
||||
});
|
||||
definePageMeta({
|
||||
layout: "auth",
|
||||
});
|
||||
|
||||
const authStore = useAuthStore();
|
||||
authStore.setActiveIdentity(null);
|
||||
|
||||
const formSchema = toTypedSchema(
|
||||
z
|
||||
.object({
|
||||
token: z.string(),
|
||||
password: z
|
||||
.string()
|
||||
.min(3, {
|
||||
message: m.smart_bold_macaw_aid({
|
||||
count: 3,
|
||||
}),
|
||||
})
|
||||
.max(100, {
|
||||
message: m.dry_smug_goldfish_promise({
|
||||
count: 100,
|
||||
}),
|
||||
}),
|
||||
"password-confirm": z
|
||||
.string()
|
||||
.min(3, {
|
||||
message: m.smart_bold_macaw_aid({
|
||||
count: 3,
|
||||
}),
|
||||
})
|
||||
.max(100, {
|
||||
message: m.dry_smug_goldfish_promise({
|
||||
count: 100,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (data.password !== data["password-confirm"]) {
|
||||
ctx.addIssue({
|
||||
path: [...ctx.path, "password-confirm"],
|
||||
code: "custom",
|
||||
message: m.candid_fancy_leopard_prosper(),
|
||||
});
|
||||
}
|
||||
return {};
|
||||
}),
|
||||
);
|
||||
|
||||
const params = useUrlSearchParams();
|
||||
|
||||
const form = useForm({
|
||||
validationSchema: formSchema,
|
||||
initialValues: {
|
||||
token: (params.token as string) ?? undefined,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
{{ errors.error }}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<CardTitle as="h1" class="text-2xl break-words">{{ m.wide_topical_vole_walk() }}</CardTitle>
|
||||
<CardTitle as="h1" class="text-2xl wrap-break-word">{{ m.wide_topical_vole_walk() }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent v-if="instance && tos" class="grid gap-6">
|
||||
<FormField v-slot="{ componentField }" name="username">
|
||||
|
|
@ -74,7 +74,7 @@
|
|||
<Dialog>
|
||||
{{ m.plane_quick_chipmunk_rush() }} <DialogTrigger :as-child="true"><Button variant="link"
|
||||
class="px-0 underline">{{ m.glad_last_crow_dine() }}</Button>.</DialogTrigger>
|
||||
<DialogContent class="!max-h-[90vh] overflow-auto">
|
||||
<DialogContent class="max-h-[90vh]! overflow-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ instance.title }}
|
||||
</DialogTitle>
|
||||
|
|
@ -136,7 +136,7 @@ useHead({
|
|||
const schema = toTypedSchema(
|
||||
z
|
||||
.object({
|
||||
email: z.string().email(),
|
||||
email: z.email(),
|
||||
password: z.string().min(3).max(255),
|
||||
"password-confirm": z.string().min(3).max(255),
|
||||
username: z
|
||||
|
|
@ -151,12 +151,11 @@ const schema = toTypedSchema(
|
|||
.superRefine((data, ctx) => {
|
||||
if (data.password !== data["password-confirm"]) {
|
||||
ctx.addIssue({
|
||||
path: [...ctx.path, "password-confirm"],
|
||||
path: ["password-confirm"],
|
||||
code: "custom",
|
||||
message: m.candid_fancy_leopard_prosper(),
|
||||
});
|
||||
}
|
||||
return {};
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue