added auth support #1
7 changed files with 112 additions and 3 deletions
31
app/components/forms/auth/login-form.vue
Normal file
31
app/components/forms/auth/login-form.vue
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
const email = ref("");
|
||||||
|
const password = ref("");
|
||||||
|
const {login} = useAuth();
|
||||||
|
|
||||||
|
const handleLogin = () => login(email.value, password.value)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<form class="password-form" @submit.prevent="handleLogin">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email">Email</label>
|
||||||
|
<input id="email" v-model="email" type="email"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input id="password" v-model="password" type="password"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -2,11 +2,13 @@
|
||||||
const dropdownOpen = ref(false)
|
const dropdownOpen = ref(false)
|
||||||
const profileMenu = ref<HTMLElement | null>(null)
|
const profileMenu = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
function toggleDropdown() {
|
const {logout} = useAuth()
|
||||||
|
|
||||||
|
const toggleDropdown = () => {
|
||||||
dropdownOpen.value = !dropdownOpen.value
|
dropdownOpen.value = !dropdownOpen.value
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClickOutside(e: MouseEvent) {
|
const onClickOutside = (e: MouseEvent) => {
|
||||||
if (profileMenu.value && !profileMenu.value.contains(e.target as Node)) {
|
if (profileMenu.value && !profileMenu.value.contains(e.target as Node)) {
|
||||||
dropdownOpen.value = false
|
dropdownOpen.value = false
|
||||||
}
|
}
|
||||||
|
|
@ -26,7 +28,7 @@ onUnmounted(() => document.removeEventListener('click', onClickOutside))
|
||||||
<li>
|
<li>
|
||||||
<NuxtLink to="/account" @click="dropdownOpen = false">Account</NuxtLink>
|
<NuxtLink to="/account" @click="dropdownOpen = false">Account</NuxtLink>
|
||||||
</li>
|
</li>
|
||||||
<li>Log Out</li>
|
<li @click="logout">Log Out</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
17
app/composables/$api.ts
Normal file
17
app/composables/$api.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
export const $api = <T>(
|
||||||
|
path: string,
|
||||||
|
options: Parameters<typeof $fetch<T>>[1] = {}
|
||||||
|
) => {
|
||||||
|
const config = useRuntimeConfig()
|
||||||
|
const xsrfToken = useCookie('XSRF-TOKEN')
|
||||||
|
|
||||||
|
return $fetch<T>(path, {
|
||||||
|
baseURL: config.public.apiBase,
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
...(xsrfToken.value ? {'X-XSRF-TOKEN': xsrfToken.value} : {}),
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
21
app/composables/useApiData.ts
Normal file
21
app/composables/useApiData.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
export const useApiData = <T>(
|
||||||
|
url: string | Ref<string>,
|
||||||
|
options: Parameters<typeof useFetch<T>>[1] = {}
|
||||||
|
) => {
|
||||||
|
const config = useRuntimeConfig()
|
||||||
|
const xsrfToken = useCookie('XSRF-TOKEN')
|
||||||
|
const requestHeaders = useRequestHeaders(['cookie'])
|
||||||
|
|
||||||
|
return useFetch<T>(url, {
|
||||||
|
baseURL: config.public.apiBase,
|
||||||
|
credentials: 'include',
|
||||||
|
watch: false,
|
||||||
|
server: false,
|
||||||
|
headers: computed(() => ({
|
||||||
|
Accept: 'application/json',
|
||||||
|
...requestHeaders,
|
||||||
|
...(xsrfToken.value ? {'X-XSRF-TOKEN': xsrfToken.value} : {}),
|
||||||
|
})),
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
20
app/composables/useAuth.ts
Normal file
20
app/composables/useAuth.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
export const useAuth = () => {
|
||||||
|
const config = useRuntimeConfig()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const login = async (email: string, password: string) => {
|
||||||
|
await $fetch('/sanctum/csrf-cookie', {
|
||||||
|
baseURL: config.public.apiBase,
|
||||||
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
await $api('/api/login', {method: 'POST', body: {email, password}})
|
||||||
|
await router.push('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
const logout = async () => {
|
||||||
|
await $api('/api/logout', {method: 'POST'})
|
||||||
|
await router.push('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {login, logout}
|
||||||
|
}
|
||||||
13
app/pages/auth/login.vue
Normal file
13
app/pages/auth/login.vue
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
import LoginForm from "~/components/forms/auth/login-form.vue";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<login-form/>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
compatibilityDate: '2025-07-15',
|
compatibilityDate: '2025-07-15',
|
||||||
|
runtimeConfig: {
|
||||||
|
public: {
|
||||||
|
apiBase: '', // overridden by NUXT_PUBLIC_API_BASE
|
||||||
|
},
|
||||||
|
},
|
||||||
devtools: {enabled: true},
|
devtools: {enabled: true},
|
||||||
css: ['~/assets/css/reset.css'],
|
css: ['~/assets/css/reset.css'],
|
||||||
modules: [
|
modules: [
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue