Init
commit
aac376bb56
|
|
@ -0,0 +1,24 @@
|
|||
# Nuxt dev/build outputs
|
||||
.output
|
||||
.data
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
dist
|
||||
|
||||
# Node dependencies
|
||||
node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.fleet
|
||||
.idea
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
# Nuxt 3 Minimal Starter
|
||||
|
||||
Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||
|
||||
## Setup
|
||||
|
||||
Make sure to install the dependencies:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm install
|
||||
|
||||
# pnpm
|
||||
pnpm install
|
||||
|
||||
# yarn
|
||||
yarn install
|
||||
|
||||
# bun
|
||||
bun install
|
||||
```
|
||||
|
||||
## Development Server
|
||||
|
||||
Start the development server on `http://localhost:3000`:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run dev
|
||||
|
||||
# pnpm
|
||||
pnpm run dev
|
||||
|
||||
# yarn
|
||||
yarn dev
|
||||
|
||||
# bun
|
||||
bun run dev
|
||||
```
|
||||
|
||||
## Production
|
||||
|
||||
Build the application for production:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run build
|
||||
|
||||
# pnpm
|
||||
pnpm run build
|
||||
|
||||
# yarn
|
||||
yarn build
|
||||
|
||||
# bun
|
||||
bun run build
|
||||
```
|
||||
|
||||
Locally preview production build:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run preview
|
||||
|
||||
# pnpm
|
||||
pnpm run preview
|
||||
|
||||
# yarn
|
||||
yarn preview
|
||||
|
||||
# bun
|
||||
bun run preview
|
||||
```
|
||||
|
||||
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
export default defineAppConfig({
|
||||
ui: {
|
||||
primary: 'sky',
|
||||
gray: 'cool',
|
||||
},
|
||||
})
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
|
||||
<UNotifications />
|
||||
</template>
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<script setup lang="ts">
|
||||
import type { NavItem } from '@nuxt/content/dist/runtime/types'
|
||||
|
||||
const navigation = inject<Ref<NavItem[]>>('navigation')
|
||||
|
||||
const links = [{
|
||||
label: 'Documentation',
|
||||
icon: 'i-heroicons-book-open',
|
||||
to: '/getting-started',
|
||||
}, {
|
||||
label: 'Pro',
|
||||
icon: 'i-heroicons-square-3-stack-3d',
|
||||
to: '/pro',
|
||||
}, {
|
||||
label: 'Releases',
|
||||
icon: 'i-heroicons-rocket-launch',
|
||||
to: 'https://github.com/nuxt/ui/releases',
|
||||
target: '_blank',
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UHeader :links="links">
|
||||
<template #logo>
|
||||
<Logo class="h-6 w-auto" />
|
||||
</template>
|
||||
|
||||
<template #right>
|
||||
<UColorModeButton />
|
||||
|
||||
<UButton to="https://github.com/nuxt/ui" target="_blank" icon="i-simple-icons-github" color="gray" variant="ghost" />
|
||||
</template>
|
||||
|
||||
<template #panel>
|
||||
<UNavigationTree :links="mapContentNavigation(navigation)" />
|
||||
</template>
|
||||
</UHeader>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<div>
|
||||
<span class="text-primary">Nuxt</span> Breeze
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<script setup lang="ts">
|
||||
const links = [
|
||||
{
|
||||
label: 'Company',
|
||||
icon: 'i-heroicons-building-office-2',
|
||||
children: [
|
||||
{
|
||||
label: 'Overview',
|
||||
to: '/partner/overview',
|
||||
icon: 'i-heroicons-eye',
|
||||
},
|
||||
{
|
||||
label: 'Add Company',
|
||||
to: '/pro/components/docs/docs-search',
|
||||
icon: 'i-heroicons-plus-circle',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'People',
|
||||
to: '/pro/components/docs/docs-search-button',
|
||||
icon: 'i-heroicons-user-group',
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UContainer>
|
||||
<UNavigationTree :links="links" />
|
||||
</UContainer>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
export interface User {
|
||||
name: string
|
||||
email?: string
|
||||
}
|
||||
|
||||
export interface LoginCredentials {
|
||||
email: string
|
||||
password: string
|
||||
remember?: boolean
|
||||
}
|
||||
|
||||
export interface RegisterCredentials {
|
||||
name: string
|
||||
email: string
|
||||
password: string
|
||||
password_confirmation: string
|
||||
}
|
||||
|
||||
export interface ResetPasswordCredentials {
|
||||
email: string
|
||||
password: string
|
||||
password_confirmation: string
|
||||
token: string
|
||||
}
|
||||
|
||||
// Value is initialized in: ~/plugins/auth.ts
|
||||
export function useUser<T = User>() {
|
||||
return useState<T | undefined | null>('user', () => undefined)
|
||||
}
|
||||
|
||||
export function useAuth<T = User>() {
|
||||
const router = useRouter()
|
||||
|
||||
const user = useUser<T>()
|
||||
const isLoggedIn = computed(() => !!user.value)
|
||||
|
||||
async function refresh() {
|
||||
try {
|
||||
user.value = await fetchCurrentUser()
|
||||
}
|
||||
catch {
|
||||
user.value = null
|
||||
}
|
||||
}
|
||||
|
||||
async function login(credentials: LoginCredentials) {
|
||||
if (isLoggedIn.value) { return }
|
||||
|
||||
await $larafetch('/login', { method: 'post', body: credentials })
|
||||
await refresh()
|
||||
}
|
||||
|
||||
async function register(credentials: RegisterCredentials) {
|
||||
await $larafetch('/register', { method: 'post', body: credentials })
|
||||
await refresh()
|
||||
}
|
||||
|
||||
async function resendEmailVerification() {
|
||||
return await $larafetch<{ status: string }>(
|
||||
'/email/verification-notification',
|
||||
{
|
||||
method: 'post',
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
if (!isLoggedIn.value) { return }
|
||||
|
||||
await $larafetch('/logout', { method: 'post' })
|
||||
user.value = null
|
||||
|
||||
await router.push('/login')
|
||||
}
|
||||
|
||||
async function forgotPassword(email: string) {
|
||||
return await $larafetch<{ status: string }>('/forgot-password', {
|
||||
method: 'post',
|
||||
body: { email },
|
||||
})
|
||||
}
|
||||
|
||||
async function resetPassword(credentials: ResetPasswordCredentials) {
|
||||
return await $larafetch<{ status: string }>('/reset-password', {
|
||||
method: 'post',
|
||||
body: credentials,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
isLoggedIn,
|
||||
login,
|
||||
register,
|
||||
resendEmailVerification,
|
||||
logout,
|
||||
forgotPassword,
|
||||
resetPassword,
|
||||
refresh,
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchCurrentUser<T = User>() {
|
||||
try {
|
||||
return await $larafetch<T>('/api/user')
|
||||
}
|
||||
catch (error: any) {
|
||||
if ([401, 419].includes(error?.response?.status)) { return null }
|
||||
if (error?.response?.status === undefined) { return null }
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import type { UseFetchOptions } from 'nuxt/app'
|
||||
|
||||
export function useLarafetch<T>(
|
||||
url: string | (() => string),
|
||||
options: UseFetchOptions<T> = {},
|
||||
) {
|
||||
return useFetch(url, {
|
||||
$fetch: $larafetch,
|
||||
async onResponseError({ response }) {
|
||||
const status = response.status
|
||||
if ([500].includes(status)) {
|
||||
console.error('[Laravel Error]', response.statusText, response._data)
|
||||
}
|
||||
|
||||
if ([401, 419].includes(status)) {
|
||||
navigateTo('/login')
|
||||
}
|
||||
|
||||
if ([409].includes(status)) {
|
||||
navigateTo('/verify-email')
|
||||
}
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
import type { FormError } from '@nuxt/ui/dist/runtime/types'
|
||||
|
||||
export interface UseSubmitOptions {
|
||||
onSuccess?: (result: any) => any
|
||||
onError?: (error: Error) => any
|
||||
}
|
||||
|
||||
export function useSubmit<T>(
|
||||
fetchable: () => Promise<T>,
|
||||
options: UseSubmitOptions = {},
|
||||
) {
|
||||
const validationErrors = ref<FormError[]>([])
|
||||
const error = ref<Error | null>(null)
|
||||
const inProgress = ref(false)
|
||||
const succeeded = ref<boolean | null>(null)
|
||||
|
||||
async function submit() {
|
||||
validationErrors.value = []
|
||||
error.value = null
|
||||
inProgress.value = true
|
||||
succeeded.value = null
|
||||
|
||||
try {
|
||||
const data = await fetchable()
|
||||
succeeded.value = true
|
||||
options?.onSuccess?.(data)
|
||||
return data
|
||||
}
|
||||
catch (e: any) {
|
||||
error.value = e
|
||||
succeeded.value = false
|
||||
options?.onError?.(e)
|
||||
if (e.data?.errors) {
|
||||
const errorsArray: FormError[] = []
|
||||
for (const path in e.data.errors) {
|
||||
const messages = e.data.errors[path]
|
||||
messages.forEach((message: string) => {
|
||||
errorsArray.push({ path, message })
|
||||
})
|
||||
}
|
||||
validationErrors.value = errorsArray
|
||||
}
|
||||
else {
|
||||
validationErrors.value = []
|
||||
}
|
||||
|
||||
if (e.response?.status !== 422) { throw e }
|
||||
}
|
||||
finally {
|
||||
inProgress.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
submit,
|
||||
inProgress,
|
||||
succeeded,
|
||||
validationErrors,
|
||||
error,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import antfu from '@antfu/eslint-config'
|
||||
import { FlatCompat } from '@eslint/eslintrc'
|
||||
|
||||
const compat = new FlatCompat()
|
||||
|
||||
export default antfu({
|
||||
rules: {
|
||||
'curly': ['error', 'all'],
|
||||
'node/prefer-global/process': ['error', 'always'],
|
||||
},
|
||||
}, ...compat.config({
|
||||
extends: ['plugin:tailwindcss/recommended'],
|
||||
rules: {
|
||||
'tailwindcss/no-custom-classname': 'off',
|
||||
'tailwindcss/migration-from-tailwind-2': 'off',
|
||||
},
|
||||
}))
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<template>
|
||||
<div>
|
||||
<Header />
|
||||
<UPage>
|
||||
<template #left>
|
||||
<UAside class="lg:static">
|
||||
<Navigation />
|
||||
</UAside>
|
||||
</template>
|
||||
<UPageBody>
|
||||
<UContainer>
|
||||
<slot />
|
||||
</UContainer>
|
||||
</UPageBody>
|
||||
</UPage>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
export default defineNuxtRouteMiddleware(async () => {
|
||||
const user = useUser();
|
||||
if (!user.value) return navigateTo("/login", { replace: true });
|
||||
});
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
export default defineNuxtRouteMiddleware(async () => {
|
||||
const user = useUser();
|
||||
if (user.value) return navigateTo("/", { replace: true });
|
||||
});
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
export default defineNuxtRouteMiddleware(() => {
|
||||
const user = useUser();
|
||||
|
||||
if (!user.value) return navigateTo("/login");
|
||||
|
||||
// @ts-ignore
|
||||
if (user.value.email_verified_at || user.value.is_verified)
|
||||
return navigateTo("/");
|
||||
});
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
export default defineNuxtRouteMiddleware(() => {
|
||||
const user = useUser();
|
||||
|
||||
if (!user.value) return navigateTo("/login");
|
||||
|
||||
// @ts-ignore
|
||||
if (!(user.value.email_verified_at || user.value.is_verified))
|
||||
return navigateTo("/verify-email");
|
||||
});
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
extends: ['@nuxt/ui-pro'],
|
||||
modules: ['@nuxt/ui'],
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
backendUrl: 'http://localhost',
|
||||
frontendUrl: 'http://localhost:3000',
|
||||
},
|
||||
},
|
||||
imports: {
|
||||
dirs: ['./utils'],
|
||||
},
|
||||
devtools: { enabled: true },
|
||||
})
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "nuxt-app",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^1.1.0",
|
||||
"@iconify-json/heroicons": "^1.1.13",
|
||||
"@iconify-json/logos": "^1.1.37",
|
||||
"@iconify-json/simple-icons": "^1.1.76",
|
||||
"@nuxt/devtools": "latest",
|
||||
"@nuxt/ui-pro": "^0.4.2",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint-plugin-tailwindcss": "^3.13.0",
|
||||
"nuxt": "^3.8.1",
|
||||
"typescript": "^5.2.2",
|
||||
"vue": "^3.3.8",
|
||||
"vue-router": "^4.2.5"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
<script setup lang="ts">
|
||||
definePageMeta({ middleware: ['guest'] })
|
||||
|
||||
const router = useRouter()
|
||||
const { forgotPassword } = useAuth()
|
||||
|
||||
const form = ref()
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
const state = ref({
|
||||
email: '' as string,
|
||||
})
|
||||
|
||||
const status = ref('')
|
||||
|
||||
const {
|
||||
submit,
|
||||
inProgress,
|
||||
validationErrors,
|
||||
} = useSubmit(
|
||||
() => {
|
||||
status.value = ''
|
||||
return forgotPassword(state.value.email)
|
||||
},
|
||||
{
|
||||
onSuccess: () => router.push('/login'),
|
||||
onError: error => toast.add({ title: 'Error', description: error.message, color: 'red' }),
|
||||
},
|
||||
)
|
||||
|
||||
watch(validationErrors, async (errors) => {
|
||||
if (errors.length > 0) {
|
||||
await form.value?.setErrors(errors)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UMain>
|
||||
<UPage>
|
||||
<div class="mx-auto flex min-h-screen w-full items-center justify-center">
|
||||
<UCard class="w-96">
|
||||
<template #header>
|
||||
<div class="space-y-4 text-center ">
|
||||
<h1 class="text-2xl font-bold">
|
||||
Forgot Password
|
||||
</h1>
|
||||
<p class="text-sm">
|
||||
Remember your password? <NuxtLink to="/login" class="text-primary hover:text-primary-300 font-semibold">
|
||||
Login here
|
||||
</NuxtLink>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<UForm ref="form" :state="state" class="space-y-8" @submit="submit">
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
<UButton block size="md" type="submit" :loading="inProgress" icon="i-heroicons-envelope">
|
||||
Reset Password
|
||||
</UButton>
|
||||
</UForm>
|
||||
|
||||
<!-- <UDivider label="OR" class=" my-4"/> -->
|
||||
</UCard>
|
||||
</div>
|
||||
</UPage>
|
||||
</UMain>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
definePageMeta({ middleware: ['auth'], layout: 'app' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
This is the Page Content
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
<script setup lang="ts">
|
||||
definePageMeta({ middleware: ['guest'] })
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { login } = useAuth()
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
const form = ref()
|
||||
|
||||
const state = ref<LoginCredentials>({
|
||||
email: '',
|
||||
password: '',
|
||||
remember: false,
|
||||
})
|
||||
|
||||
const status = ref(
|
||||
(route.query.reset ?? '').length > 0 ? atob(route.query.reset as string) : '',
|
||||
)
|
||||
|
||||
const {
|
||||
submit,
|
||||
inProgress,
|
||||
validationErrors,
|
||||
} = useSubmit(
|
||||
() => {
|
||||
status.value = ''
|
||||
return login(state.value)
|
||||
},
|
||||
{
|
||||
onSuccess: () => router.push('/'),
|
||||
onError: (error) => {
|
||||
toast.add({ title: 'Error', description: error.message, color: 'red' })
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
watch(validationErrors, async (errors) => {
|
||||
if (errors.length > 0) {
|
||||
await form.value?.setErrors(errors)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UMain>
|
||||
<UPage>
|
||||
<div class="mx-auto flex min-h-screen w-full items-center justify-center">
|
||||
<UCard class="w-96">
|
||||
<template #header>
|
||||
<h1 class="text-center text-2xl font-bold">
|
||||
Login
|
||||
</h1>
|
||||
</template>
|
||||
|
||||
<UForm ref="form" :state="state" class="space-y-4" @submit="submit">
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormGroup>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<UCheckbox id="remember-me" v-model="state.remember" label="Remember me" name="remember-me" />
|
||||
|
||||
<div class="text-sm leading-6">
|
||||
<NuxtLink to="/forgot-password" class="text-primary hover:text-primary-300 font-semibold">
|
||||
Forgot
|
||||
password?
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<UButton block size="md" type="submit" :loading="inProgress" icon="i-heroicons-arrow-right-on-rectangle">
|
||||
Login
|
||||
</UButton>
|
||||
</UForm>
|
||||
|
||||
<!-- <UDivider label="OR" class=" my-4"/> -->
|
||||
</UCard>
|
||||
</div>
|
||||
</UPage>
|
||||
</UMain>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
<script setup lang="ts">
|
||||
definePageMeta({ middleware: ['guest'] })
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { resetPassword } = useAuth()
|
||||
|
||||
if (!route.query.email) {
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
const state = ref<ResetPasswordCredentials>({
|
||||
email: route.query.email as string,
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
token: route.params.token as string,
|
||||
})
|
||||
|
||||
const status = ref(
|
||||
(route.query.reset ?? '').length > 0 ? atob(route.query.reset as string) : '',
|
||||
)
|
||||
|
||||
const form = ref()
|
||||
|
||||
const {
|
||||
submit,
|
||||
inProgress,
|
||||
validationErrors,
|
||||
} = useSubmit(
|
||||
() => {
|
||||
status.value = ''
|
||||
return resetPassword(state.value)
|
||||
},
|
||||
{
|
||||
onSuccess: () => router.push('/'),
|
||||
onError: (error) => {
|
||||
toast.add({ title: 'Error', description: error.message, color: 'red' })
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
watch(validationErrors, async (errors) => {
|
||||
if (errors.length > 0) {
|
||||
await form.value?.setErrors(errors)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UMain>
|
||||
<UPage>
|
||||
<div class="mx-auto flex min-h-screen w-full items-center justify-center">
|
||||
<UCard class="w-96">
|
||||
<template #header>
|
||||
<h1 class="text-center text-2xl font-bold">
|
||||
Reset Password
|
||||
</h1>
|
||||
</template>
|
||||
|
||||
<UForm ref="form" :state="state" class="space-y-4" @submit="submit">
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" disabled />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Confirm Password" name="password_confirmation">
|
||||
<UInput v-model="state.password_confirmation" type="password" />
|
||||
</UFormGroup>
|
||||
|
||||
<UButton block size="md" type="submit" :loading="inProgress" icon="i-heroicon-lock-closed">
|
||||
Change Password
|
||||
</UButton>
|
||||
</UForm>
|
||||
|
||||
<!-- <UDivider label="OR" class=" my-4"/> -->
|
||||
</UCard>
|
||||
</div>
|
||||
</UPage>
|
||||
</UMain>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import { fetchCurrentUser, useUser } from '~/composables/useAuth'
|
||||
|
||||
export default defineNuxtPlugin(async () => {
|
||||
const user = useUser()
|
||||
|
||||
// Skip if already initialized on server
|
||||
if (user.value !== undefined) { return }
|
||||
|
||||
user.value = await fetchCurrentUser()
|
||||
})
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import { FetchError } from 'ofetch'
|
||||
|
||||
export default defineNuxtPlugin(async (nuxtApp) => {
|
||||
nuxtApp.hook('vue:error', (error) => {
|
||||
if (!(error instanceof FetchError)) { throw error }
|
||||
|
||||
const status = error.response?.status ?? -1
|
||||
|
||||
if ([401, 419].includes(status)) {
|
||||
navigateTo('/login')
|
||||
}
|
||||
|
||||
if ([409].includes(status)) {
|
||||
navigateTo('/verify-email')
|
||||
}
|
||||
})
|
||||
})
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "../.nuxt/tsconfig.server.json"
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
// https://nuxt.com/docs/guide/concepts/typescript
|
||||
"extends": "./.nuxt/tsconfig.json"
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
import { $fetch } from 'ofetch'
|
||||
import { parseCookies } from 'h3'
|
||||
|
||||
const CSRF_COOKIE = 'XSRF-TOKEN'
|
||||
const CSRF_HEADER = 'X-XSRF-TOKEN'
|
||||
|
||||
export const $larafetch = $fetch.create({
|
||||
credentials: 'include',
|
||||
async onRequest({ options }) {
|
||||
const { backendUrl, frontendUrl } = useRuntimeConfig().public
|
||||
const event = process.nitro ? useEvent() : null
|
||||
let token = event
|
||||
? parseCookies(event)[CSRF_COOKIE]
|
||||
: useCookie(CSRF_COOKIE).value
|
||||
|
||||
// on client initiate a csrf request and get it from the cookie set by laravel
|
||||
if (
|
||||
process.client
|
||||
&& ['post', 'delete', 'put', 'patch'].includes(
|
||||
options?.method?.toLowerCase() ?? '',
|
||||
)
|
||||
) {
|
||||
token = await initCsrf()
|
||||
}
|
||||
|
||||
let headers: any = {
|
||||
accept: 'application/json',
|
||||
...options?.headers,
|
||||
...(token && { [CSRF_HEADER]: token }),
|
||||
}
|
||||
|
||||
if (process.server) {
|
||||
const cookieString = event
|
||||
? event.headers.get('cookie')
|
||||
: useRequestHeaders(['cookie']).cookie
|
||||
|
||||
headers = {
|
||||
...headers,
|
||||
...(cookieString && { cookie: cookieString }),
|
||||
referer: frontendUrl,
|
||||
}
|
||||
}
|
||||
|
||||
options.headers = headers
|
||||
options.baseURL = backendUrl
|
||||
},
|
||||
async onResponseError({ response }) {
|
||||
const status = response.status
|
||||
if ([500].includes(status)) {
|
||||
console.error('[Laravel Error]', response.statusText, response._data)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
async function initCsrf() {
|
||||
const { backendUrl } = useRuntimeConfig().public
|
||||
const existingToken = useCookie(CSRF_COOKIE).value
|
||||
|
||||
if (existingToken) { return existingToken }
|
||||
|
||||
await $fetch('/sanctum/csrf-cookie', {
|
||||
baseURL: backendUrl,
|
||||
credentials: 'include',
|
||||
})
|
||||
|
||||
return useCookie(CSRF_COOKIE).value
|
||||
}
|
||||
Loading…
Reference in New Issue