This commit is contained in:
2025-12-23 19:26:23 +01:00
commit da7e984965
94 changed files with 26350 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
<script setup lang="ts">
import type { AuthFormField, FormSubmitEvent } from '@nuxt/ui'
import { useForm } from '@inertiajs/vue3'
import * as v from 'valibot'
import { computed } from 'vue'
import AuthLayout from '@/layouts/AuthLayout.vue'
const props = defineProps<{
socialiteUser: {
email: string
first_name: string
last_name: string
suggested_username: string
provider: string
}
}>()
const form = useForm({
username: props.socialiteUser.suggested_username,
first_name: props.socialiteUser.first_name,
last_name: props.socialiteUser.last_name,
})
const fields: AuthFormField[] = [
{
name: 'username',
type: 'text',
label: 'Username',
placeholder: 'Choose a unique username',
required: true,
hint: 'The suggested username was already taken. Please choose a different one.',
},
{
name: 'first_name',
type: 'text',
label: 'First name',
placeholder: 'Enter your first name',
required: true,
},
{
name: 'last_name',
type: 'text',
label: 'Last name',
placeholder: 'Enter your last name',
required: true,
},
]
const schema = v.object({
username: v.pipe(
v.string('Username is required'),
v.nonEmpty('Username is required'),
v.minLength(3, 'Username must be at least 3 characters'),
v.regex(/^[\w-]+$/, 'Username can only contain letters, numbers, dashes and underscores'),
),
first_name: v.pipe(v.string('First name is required'), v.nonEmpty('First name is required')),
last_name: v.pipe(v.string('Last name is required'), v.nonEmpty('Last name is required')),
})
type Schema = v.InferOutput<typeof schema>
function onSubmit(event: FormSubmitEvent<Schema>) {
form.username = event.data.username
form.first_name = event.data.first_name
form.last_name = event.data.last_name
form.post('/complete-profile')
}
const hasErrors = computed(() => Object.keys(form.errors).length > 0)
const firstError = computed(() => Object.values(form.errors)[0])
</script>
<template>
<AuthLayout>
<UAuthForm
:schema="schema"
:fields="fields"
:submit="{ label: 'Complete registration', loading: form.processing }"
:disabled="form.processing"
:ui="{ header: 'hidden' }"
@submit="onSubmit"
>
<template v-if="hasErrors" #validation>
<UAlert
color="error"
icon="i-lucide-alert-circle"
:title="firstError"
/>
</template>
<template #footer>
<span class="text-muted">
Signing up with {{ socialiteUser.email }}
</span>
</template>
</UAuthForm>
</AuthLayout>
</template>

View File

@@ -0,0 +1,75 @@
<script setup lang="ts">
import type { AuthFormField, FormSubmitEvent } from '@nuxt/ui'
import { useForm } from '@inertiajs/vue3'
import * as v from 'valibot'
import { computed } from 'vue'
import { useAuth } from '@/composables/useAuth'
import AuthLayout from '@/layouts/AuthLayout.vue'
const { config, flash } = useAuth()
const form = useForm({
email: '',
})
const fields: AuthFormField[] = [
{
name: 'email',
type: 'email',
label: 'Email',
placeholder: 'Enter your email',
required: true,
},
]
const schema = v.object({
email: v.pipe(v.string('Email is required'), v.nonEmpty('Email is required'), v.email('Please enter a valid email')),
})
type Schema = v.InferOutput<typeof schema>
function onSubmit(event: FormSubmitEvent<Schema>) {
form.email = event.data.email
form.post('/forgot-password')
}
const hasErrors = computed(() => Object.keys(form.errors).length > 0)
const firstError = computed(() => Object.values(form.errors)[0])
</script>
<template>
<AuthLayout>
<UAuthForm
:schema="schema"
:fields="fields"
:submit="{ label: config.forgotPassword.submit_label, loading: form.processing }"
:disabled="form.processing"
:ui="{ header: 'hidden' }"
@submit="onSubmit"
>
<template v-if="hasErrors || flash.status" #validation>
<UAlert
v-if="hasErrors"
color="error"
icon="i-lucide-alert-circle"
:title="firstError"
/>
<UAlert
v-if="flash.status"
color="success"
icon="i-lucide-check-circle"
:title="flash.status"
/>
</template>
<template #footer>
<span class="text-muted">
Remember your password?
<ULink to="/login" class="text-primary font-medium">
Sign in
</ULink>
</span>
</template>
</UAuthForm>
</AuthLayout>
</template>

View File

@@ -0,0 +1,111 @@
<script setup lang="ts">
import type { AuthFormField, FormSubmitEvent } from '@nuxt/ui'
import { useForm } from '@inertiajs/vue3'
import * as v from 'valibot'
import { computed, ref } from 'vue'
import { useAuth } from '@/composables/useAuth'
import AuthLayout from '@/layouts/AuthLayout.vue'
const { config, flash } = useAuth()
const form = useForm({
email: '',
password: '',
remember: false,
})
const rememberMe = ref(false)
const fields: AuthFormField[] = [
{
name: 'email',
type: 'email',
label: 'Email',
placeholder: 'Enter your email',
required: true,
},
{
name: 'password',
type: 'password',
label: 'Password',
placeholder: 'Enter your password',
required: true,
},
]
const providers = computed(() =>
config.value.providers.map(provider => ({
label: provider.label,
icon: provider.icon,
to: `/auth/${provider.key}`,
})),
)
const schema = v.object({
email: v.pipe(v.string('Email is required'), v.nonEmpty('Email is required'), v.email('Please enter a valid email')),
password: v.pipe(v.string('Password is required'), v.nonEmpty('Password is required')),
remember: v.optional(v.boolean()),
})
type Schema = v.InferOutput<typeof schema>
function onSubmit(event: FormSubmitEvent<Schema>) {
form.email = event.data.email
form.password = event.data.password
form.remember = rememberMe.value
form.post('/login')
}
</script>
<template>
<AuthLayout>
<UAuthForm
:schema="schema"
:fields="fields"
:providers="providers"
:submit="{ label: config.login.submit_label, loading: form.processing }"
:disabled="form.processing"
:ui="{ header: 'hidden' }"
@submit="onSubmit"
>
<template v-if="config.features.password_reset" #password-hint>
<ULink to="/forgot-password" class="text-primary font-medium" tabindex="-1">
Forgot password?
</ULink>
</template>
<template v-if="form.errors.email || flash.status" #validation>
<UAlert
v-if="form.errors.email"
color="error"
icon="i-lucide-alert-circle"
:title="form.errors.email"
/>
<UAlert
v-if="flash.status"
color="success"
icon="i-lucide-check-circle"
:title="flash.status"
/>
</template>
<template #footer>
<div class="flex items-center justify-between">
<UCheckbox
v-if="config.features.remember_me"
v-model="rememberMe"
label="Remember me"
/>
<span v-else />
<span v-if="config.features.registration" class="text-sm text-muted">
Don't have an account?
<ULink to="/register" class="text-primary font-medium">
Sign up
</ULink>
</span>
</div>
</template>
</UAuthForm>
</AuthLayout>
</template>

View File

@@ -0,0 +1,155 @@
<script setup lang="ts">
import type { AuthFormField, FormSubmitEvent } from '@nuxt/ui'
import { useForm } from '@inertiajs/vue3'
import * as v from 'valibot'
import { computed } from 'vue'
import { useAuth } from '@/composables/useAuth'
import AuthLayout from '@/layouts/AuthLayout.vue'
const { config } = useAuth()
const form = useForm({
username: '',
first_name: '',
last_name: '',
email: '',
password: '',
password_confirmation: '',
})
const fields: AuthFormField[] = [
{
name: 'username',
type: 'text',
label: 'Username',
placeholder: 'Choose a username',
required: true,
},
{
name: 'first_name',
type: 'text',
label: 'First name',
placeholder: 'Enter your first name',
required: true,
},
{
name: 'last_name',
type: 'text',
label: 'Last name',
placeholder: 'Enter your last name',
required: true,
},
{
name: 'email',
type: 'email',
label: 'Email',
placeholder: 'Enter your email',
required: true,
},
{
name: 'password',
type: 'password',
label: 'Password',
placeholder: 'Create a password',
required: true,
},
{
name: 'password_confirmation',
type: 'password',
label: 'Confirm password',
placeholder: 'Confirm your password',
required: true,
},
]
const providers = computed(() =>
config.value.providers.map(provider => ({
label: provider.label,
icon: provider.icon,
to: `/auth/${provider.key}`,
})),
)
const schema = v.pipe(
v.object({
username: v.pipe(
v.string('Username is required'),
v.nonEmpty('Username is required'),
v.minLength(3, 'Username must be at least 3 characters'),
v.regex(/^[\w-]+$/, 'Username can only contain letters, numbers, dashes and underscores'),
),
first_name: v.pipe(v.string('First name is required'), v.nonEmpty('First name is required')),
last_name: v.pipe(v.string('Last name is required'), v.nonEmpty('Last name is required')),
email: v.pipe(v.string('Email is required'), v.nonEmpty('Email is required'), v.email('Please enter a valid email')),
password: v.pipe(v.string('Password is required'), v.nonEmpty('Password is required'), v.minLength(8, 'Password must be at least 8 characters')),
password_confirmation: v.pipe(v.string('Please confirm your password'), v.nonEmpty('Please confirm your password')),
}),
v.forward(
v.partialCheck(
[['password'], ['password_confirmation']],
input => input.password === input.password_confirmation,
'Passwords do not match',
),
['password_confirmation'],
),
)
type Schema = v.InferOutput<typeof schema>
function onSubmit(event: FormSubmitEvent<Schema>) {
form.username = event.data.username
form.first_name = event.data.first_name
form.last_name = event.data.last_name
form.email = event.data.email
form.password = event.data.password
form.password_confirmation = event.data.password_confirmation
form.post('/register')
}
const hasErrors = computed(() => Object.keys(form.errors).length > 0)
const firstError = computed(() => Object.values(form.errors)[0])
</script>
<template>
<AuthLayout>
<UAuthForm
:schema="schema"
:fields="fields"
:providers="providers"
:submit="{ label: config.register.submit_label, loading: form.processing }"
:disabled="form.processing"
:ui="{ header: 'hidden' }"
@submit="onSubmit"
>
<template v-if="hasErrors" #validation>
<UAlert
color="error"
icon="i-lucide-alert-circle"
:title="firstError"
/>
</template>
<template #footer>
<div v-if="config.legal.show_in_register && (config.legal.terms_url || config.legal.privacy_url)" class="mb-2">
By creating an account, you agree to our
<ULink v-if="config.legal.terms_url" :to="config.legal.terms_url" class="text-primary font-medium">
Terms of Service
</ULink>
<template v-if="config.legal.terms_url && config.legal.privacy_url">
and
</template>
<ULink v-if="config.legal.privacy_url" :to="config.legal.privacy_url" class="text-primary font-medium">
Privacy Policy
</ULink>.
</div>
<span class="text-muted">
Already have an account?
<ULink to="/login" class="text-primary font-medium">
Sign in
</ULink>
</span>
</template>
</UAuthForm>
</AuthLayout>
</template>

View File

@@ -0,0 +1,105 @@
<script setup lang="ts">
import type { AuthFormField, FormSubmitEvent } from '@nuxt/ui'
import { useForm } from '@inertiajs/vue3'
import * as v from 'valibot'
import { computed } from 'vue'
import { useAuth } from '@/composables/useAuth'
import AuthLayout from '@/layouts/AuthLayout.vue'
const props = defineProps<{
email: string
token: string
}>()
const { config } = useAuth()
const form = useForm({
token: props.token,
email: props.email,
password: '',
password_confirmation: '',
})
const fields: AuthFormField[] = [
{
name: 'email',
type: 'email',
label: 'Email',
placeholder: 'Enter your email',
required: true,
},
{
name: 'password',
type: 'password',
label: 'New password',
placeholder: 'Enter your new password',
required: true,
},
{
name: 'password_confirmation',
type: 'password',
label: 'Confirm password',
placeholder: 'Confirm your new password',
required: true,
},
]
const schema = v.pipe(
v.object({
email: v.pipe(v.string('Email is required'), v.nonEmpty('Email is required'), v.email('Please enter a valid email')),
password: v.pipe(v.string('Password is required'), v.nonEmpty('Password is required'), v.minLength(8, 'Password must be at least 8 characters')),
password_confirmation: v.pipe(v.string('Please confirm your password'), v.nonEmpty('Please confirm your password')),
}),
v.forward(
v.partialCheck(
[['password'], ['password_confirmation']],
input => input.password === input.password_confirmation,
'Passwords do not match',
),
['password_confirmation'],
),
)
type Schema = v.InferOutput<typeof schema>
function onSubmit(event: FormSubmitEvent<Schema>) {
form.email = event.data.email
form.password = event.data.password
form.password_confirmation = event.data.password_confirmation
form.post('/reset-password')
}
const hasErrors = computed(() => Object.keys(form.errors).length > 0)
const firstError = computed(() => Object.values(form.errors)[0])
</script>
<template>
<AuthLayout>
<UAuthForm
:schema="schema"
:fields="fields"
:submit="{ label: config.resetPassword.submit_label, loading: form.processing }"
:disabled="form.processing"
:ui="{ header: 'hidden' }"
@submit="onSubmit"
>
<template v-if="hasErrors" #validation>
<UAlert
color="error"
icon="i-lucide-alert-circle"
:title="firstError"
/>
</template>
<template #footer>
<span class="text-muted">
Remember your password?
<ULink to="/login" class="text-primary font-medium">
Sign in
</ULink>
</span>
</template>
</UAuthForm>
</AuthLayout>
</template>

View File

@@ -0,0 +1,60 @@
<script setup lang="ts">
import { useForm } from '@inertiajs/vue3'
import { useAuth } from '@/composables/useAuth'
defineProps<{
appName: string
}>()
const { user, isAuthenticated, config } = useAuth()
const logoutForm = useForm({})
function logout() {
logoutForm.post('/logout')
}
</script>
<template>
<UApp>
<div class="min-h-screen flex flex-col bg-gray-100 dark:bg-gray-900">
<header class="flex items-center justify-end gap-4 p-4">
<template v-if="isAuthenticated">
<span class="text-sm text-gray-600 dark:text-gray-400">
{{ user?.full_name }}
</span>
<UButton variant="outline" :loading="logoutForm.processing" @click="logout">
Logout
</UButton>
</template>
<template v-else>
<UButton to="/login" variant="ghost">
Login
</UButton>
<UButton v-if="config.features.registration" to="/register">
Register
</UButton>
</template>
</header>
<div class="flex-1 flex items-center justify-center">
<div class="text-center">
<h1 class="text-4xl font-bold text-gray-900 dark:text-white">
Welcome to {{ appName }}
</h1>
<p class="mt-4 text-gray-600 dark:text-gray-400">
Inertia.js with Vue and Nuxt UI is ready to go!
</p>
<div v-if="!isAuthenticated" class="mt-8 flex gap-4 justify-center">
<UButton to="/register">
Get Started
</UButton>
<UButton variant="outline">
Learn More
</UButton>
</div>
</div>
</div>
</div>
</UApp>
</template>