feat: article editor core

This commit is contained in:
fsylvain committed 2026-09-27 02:11:38 +02:00
1 parent d493a181e1
commit a4734b2c91
55 files changed
+4186 -496

No files matched your search

+40 -12
View File
@@ -63,7 +63,8 @@ const passwordError = ref('')
const submitting = ref(false)
const passwordShown = ref(false)
const initialReason = typeof route.query.reason === 'string' ? route.query.reason : null
const initialReason =
typeof route.query.reason === 'string' ? route.query.reason : null
const alert = ref<AlertState | null>(
initialReason === 'expired'
@@ -73,7 +74,9 @@ const alert = ref<AlertState | null>(
: null,
)
const alertRole = computed(() => (alert.value?.tone === 'error' ? 'alert' : 'status'))
const alertRole = computed(() =>
alert.value?.tone === 'error' ? 'alert' : 'status',
)
const submitLabel = computed(() => {
if (submitting.value) {
@@ -85,7 +88,9 @@ const submitLabel = computed(() => {
return 'Log in'
})
const submitDisabled = computed(() => submitting.value || retryAfter.active.value)
const submitDisabled = computed(
() => submitting.value || retryAfter.active.value,
)
async function focusField(selector: string) {
await nextTick()
@@ -112,15 +117,18 @@ function focusFirstInvalid() {
}
function validate() {
usernameError.value = username.value.trim().length < 3 ? 'Use at least 3 characters.' : ''
passwordError.value = password.value.length === 0 ? 'Enter your password.' : ''
usernameError.value =
username.value.trim().length < 3 ? 'Use at least 3 characters.' : ''
passwordError.value =
password.value.length === 0 ? 'Enter your password.' : ''
return !usernameError.value && !passwordError.value
}
async function afterLoginSuccess() {
await session.retry()
const redirect = typeof route.query.redirect === 'string' ? route.query.redirect : null
const redirect =
typeof route.query.redirect === 'string' ? route.query.redirect : null
if (redirect) {
await router.push(redirect)
return
@@ -166,7 +174,10 @@ async function onSubmit() {
await api.request('/v1/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: username.value, password: password.value }),
body: JSON.stringify({
username: username.value,
password: password.value,
}),
})
await afterLoginSuccess()
} catch (error) {
@@ -182,7 +193,9 @@ onMounted(async () => {
}
try {
const status = await api.request<{ open: boolean }>('/v1/auth/registration')
const status = await api.request<{ open: boolean }>(
'/v1/auth/registration',
)
registrationOpen.value = status.open
} catch {
registrationOpen.value = false
@@ -191,7 +204,10 @@ onMounted(async () => {
</script>
<template>
<AuthShell title="Log in" description="Welcome back. Log in to manage your articles.">
<AuthShell
title="Log in"
description="Welcome back. Log in to manage your articles."
>
<template v-if="alert" #notice>
<UAlert
:color="alert.tone"
@@ -205,7 +221,11 @@ onMounted(async () => {
</template>
<template #default="{ controlSize, controlClass, inputClass }">
<UForm :state="{ username, password }" class="flex flex-col gap-4.5" @submit="onSubmit">
<UForm
:state="{ username, password }"
class="flex flex-col gap-4.5"
@submit="onSubmit"
>
<UFormField
label="Username"
name="username"
@@ -275,13 +295,21 @@ onMounted(async () => {
>
<template #trailing>
<UButton
:icon="passwordShown ? 'i-lucide-eye-off' : 'i-lucide-eye'"
:icon="
passwordShown
? 'i-lucide-eye-off'
: 'i-lucide-eye'
"
variant="ghost"
color="neutral"
size="sm"
square
class="size-8 justify-center text-toned"
:aria-label="passwordShown ? 'Hide password' : 'Show password'"
:aria-label="
passwordShown
? 'Hide password'
: 'Show password'
"
:aria-pressed="passwordShown"
@click="passwordShown = !passwordShown"
/>