diff --git a/app/components/forms/auth/login-form.vue b/app/components/forms/auth/login-form.vue
new file mode 100644
index 0000000..5632403
--- /dev/null
+++ b/app/components/forms/auth/login-form.vue
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/components/profile-menu.vue b/app/components/profile-menu.vue
index 8a0687c..93f2171 100644
--- a/app/components/profile-menu.vue
+++ b/app/components/profile-menu.vue
@@ -2,11 +2,13 @@
const dropdownOpen = ref(false)
const profileMenu = ref(null)
-function toggleDropdown() {
+const {logout} = useAuth()
+
+const toggleDropdown = () => {
dropdownOpen.value = !dropdownOpen.value
}
-function onClickOutside(e: MouseEvent) {
+const onClickOutside = (e: MouseEvent) => {
if (profileMenu.value && !profileMenu.value.contains(e.target as Node)) {
dropdownOpen.value = false
}
@@ -26,7 +28,7 @@ onUnmounted(() => document.removeEventListener('click', onClickOutside))
Account
- Log Out
+ Log Out
diff --git a/app/composables/$api.ts b/app/composables/$api.ts
new file mode 100644
index 0000000..22261e2
--- /dev/null
+++ b/app/composables/$api.ts
@@ -0,0 +1,17 @@
+export const $api = (
+ path: string,
+ options: Parameters>[1] = {}
+) => {
+ const config = useRuntimeConfig()
+ const xsrfToken = useCookie('XSRF-TOKEN')
+
+ return $fetch(path, {
+ baseURL: config.public.apiBase,
+ credentials: 'include',
+ headers: {
+ Accept: 'application/json',
+ ...(xsrfToken.value ? {'X-XSRF-TOKEN': xsrfToken.value} : {}),
+ },
+ ...options,
+ })
+}
diff --git a/app/composables/useApiData.ts b/app/composables/useApiData.ts
new file mode 100644
index 0000000..2cb0c25
--- /dev/null
+++ b/app/composables/useApiData.ts
@@ -0,0 +1,21 @@
+export const useApiData = (
+ url: string | Ref,
+ options: Parameters>[1] = {}
+) => {
+ const config = useRuntimeConfig()
+ const xsrfToken = useCookie('XSRF-TOKEN')
+ const requestHeaders = useRequestHeaders(['cookie'])
+
+ return useFetch(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,
+ })
+}
diff --git a/app/composables/useAuth.ts b/app/composables/useAuth.ts
new file mode 100644
index 0000000..d6774cd
--- /dev/null
+++ b/app/composables/useAuth.ts
@@ -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}
+}
diff --git a/app/pages/auth/login.vue b/app/pages/auth/login.vue
new file mode 100644
index 0000000..576b08c
--- /dev/null
+++ b/app/pages/auth/login.vue
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nuxt.config.ts b/nuxt.config.ts
index 6d589bd..fe5d619 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -1,6 +1,11 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
+ runtimeConfig: {
+ public: {
+ apiBase: '', // overridden by NUXT_PUBLIC_API_BASE
+ },
+ },
devtools: {enabled: true},
css: ['~/assets/css/reset.css'],
modules: [