fix safari
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled

This commit is contained in:
Nyavokevin 2025-10-03 18:57:04 +03:00
parent 4f7e5f78d5
commit eec6c974c9
12 changed files with 1191 additions and 6 deletions

328
AUTH_SETUP.md Normal file
View File

@ -0,0 +1,328 @@
# Authentication Setup Guide
This document explains the authentication system that has been set up for your Laravel + Inertia.js + Vue 3 application.
## 📦 What Was Installed/Created
### Files Created:
1. **`resources/js/services/authService.ts`** - API service for authentication
2. **`resources/js/stores/auth.ts`** - Pinia store for auth state management
3. **`resources/js/middleware/auth.ts`** - Route guards for authentication
4. **`resources/js/composables/useAuthGuard.ts`** - Composables for protecting routes
5. **`resources/js/components/LoginExample.vue`** - Example login component
### Files Modified:
- **`resources/js/app.ts`** - Integrated auth guards
## 🔧 Setup
### Note: Pinia instead of Vuex
Your project already has **Pinia** installed, which is the official state management solution for Vue 3 (and the successor to Vuex). The setup uses Pinia instead of Vuex as requested.
If you specifically need Vuex 4, run:
```bash
npm install vuex@4
```
However, Pinia is recommended for Vue 3 projects.
## 🚀 Usage
### 1. Using the Auth Store
The auth store provides all authentication functionality:
```vue
<script setup lang="ts">
import { useAuthStore } from '@/stores/auth';
const authStore = useAuthStore();
// Login
async function login() {
const success = await authStore.login({
email: 'user@example.com',
password: 'password',
remember: true
});
if (success) {
// Redirect or show success message
} else {
console.error(authStore.error);
}
}
// Logout
async function logout() {
await authStore.logout();
}
// Check authentication status
console.log(authStore.isAuthenticated);
console.log(authStore.currentUser);
</script>
```
### 2. Protecting Routes with Composables
#### Protect authenticated routes (dashboard, profile, etc.):
```vue
<script setup lang="ts">
import { useAuthGuard } from '@/composables/useAuthGuard';
// This will redirect to /login if user is not authenticated
useAuthGuard();
</script>
<template>
<div>
<h1>Protected Dashboard</h1>
</div>
</template>
```
#### Protect guest routes (login, register):
```vue
<script setup lang="ts">
import { useGuestGuard } from '@/composables/useAuthGuard';
// This will redirect to /dashboard if user is already authenticated
useGuestGuard();
</script>
<template>
<div>
<h1>Login Page</h1>
</div>
</template>
```
### 3. Accessing User Data
```vue
<script setup lang="ts">
import { useAuthStore } from '@/stores/auth';
const authStore = useAuthStore();
</script>
<template>
<div v-if="authStore.isAuthenticated">
<p>Welcome, {{ authStore.currentUser?.name }}!</p>
<p>Email: {{ authStore.currentUser?.email }}</p>
<button @click="authStore.logout">Logout</button>
</div>
<div v-else>
<p>Please log in</p>
</div>
</template>
```
### 4. Auth Store API
The auth store (`useAuthStore()`) provides:
**State:**
- `user` - Current user object or null
- `token` - Authentication token (if API returns one)
- `loading` - Loading state
- `error` - Error message
**Getters:**
- `isAuthenticated` - Boolean indicating if user is logged in
- `currentUser` - Current user object
**Actions:**
- `login(credentials)` - Login with email and password
- `logout()` - Logout current user
- `checkAuth()` - Check if user is authenticated
- `fetchUser()` - Fetch current user data
- `clearError()` - Clear error messages
- `setUser(user)` - Manually set user data
## 🔐 API Endpoints
The auth service expects these endpoints:
### Login
```
POST http://localhost:8000/api/auth/login
Body: {
"email": "user@example.com",
"password": "password",
"remember": true
}
Response: {
"user": {
"id": 1,
"name": "John Doe",
"email": "user@example.com",
...
},
"token": "optional-auth-token"
}
```
### Logout
```
POST http://localhost:8000/api/auth/logout
```
### Get Current User
```
GET http://localhost:8000/api/auth/user
Response: {
"user": {
"id": 1,
"name": "John Doe",
"email": "user@example.com",
...
}
}
```
## 🛠️ Backend Setup (Laravel)
You need to create these API routes in Laravel. Here's an example:
```php
// routes/api.php
use App\Http\Controllers\Auth\ApiAuthController;
Route::prefix('auth')->group(function () {
Route::post('/login', [ApiAuthController::class, 'login']);
Route::post('/logout', [ApiAuthController::class, 'logout'])->middleware('auth:sanctum');
Route::get('/user', [ApiAuthController::class, 'user'])->middleware('auth:sanctum');
});
```
Example controller:
```php
// app/Http/Controllers/Auth/ApiAuthController.php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class ApiAuthController extends Controller
{
public function login(Request $request)
{
$credentials = $request->validate([
'email' => 'required|email',
'password' => 'required',
]);
if (Auth::attempt($credentials, $request->boolean('remember'))) {
$user = Auth::user();
$token = $user->createToken('auth-token')->plainTextToken;
return response()->json([
'user' => $user,
'token' => $token,
]);
}
return response()->json([
'message' => 'Invalid credentials',
], 401);
}
public function logout(Request $request)
{
$request->user()->currentAccessToken()->delete();
return response()->json(['message' => 'Logged out successfully']);
}
public function user(Request $request)
{
return response()->json(['user' => $request->user()]);
}
}
```
## 💾 Persistence
The auth store automatically persists the `user` and `token` to localStorage using `pinia-plugin-persistedstate`. This means:
- User stays logged in after page refresh
- Auth state is restored on app load
- Logout clears the persisted data
## 🎯 Example: Update Your Login Page
Update your existing `resources/js/pages/auth/Login.vue`:
```vue
<script setup lang="ts">
import { useAuthStore } from '@/stores/auth';
import { useGuestGuard } from '@/composables/useAuthGuard';
import { router } from '@inertiajs/vue3';
import { ref } from 'vue';
// Redirect if already authenticated
useGuestGuard();
const authStore = useAuthStore();
const email = ref('');
const password = ref('');
const remember = ref(false);
async function handleLogin() {
const success = await authStore.login({
email: email.value,
password: password.value,
remember: remember.value,
});
if (success) {
router.get('/dashboard');
}
}
</script>
```
## 🎯 Example: Protect Dashboard
Update your dashboard to require authentication:
```vue
<script setup lang="ts">
import { useAuthGuard } from '@/composables/useAuthGuard';
// This will redirect to login if not authenticated
useAuthGuard();
</script>
<template>
<div>
<h1>Dashboard</h1>
<!-- Your dashboard content -->
</div>
</template>
```
## 📝 Notes
1. **Axios is already installed** in your project at version `1.11.0`
2. **Pinia is already installed** and configured with persistence
3. The auth system works with your existing Inertia.js setup
4. CSRF tokens are automatically handled via the existing `resources/js/lib/http.ts`
5. Authentication state persists across page reloads
## 🔄 Customization
### Change API URLs
Edit `resources/js/services/authService.ts` to change the API endpoints.
### Change Redirect Routes
Edit the composables in `resources/js/composables/useAuthGuard.ts` to change default redirect routes.
### Add More User Fields
Update the `User` interface in `resources/js/services/authService.ts` to match your user model.

293
QUICK_START_AUTH.md Normal file
View File

@ -0,0 +1,293 @@
# 🚀 Quick Start - Authentication System
## ✅ What's Been Set Up
Your Laravel + Vue 3 + Inertia.js application now has a complete authentication system with:
1. ✅ **Axios** - Already installed (v1.11.0)
2. ✅ **Pinia** - State management (already installed, replaces Vuex)
3. ✅ **Auth Service** - API calls to `http://localhost:8000/api/auth/login`
4. ✅ **Auth Store** - Centralized authentication state
5. ✅ **Route Guards** - Protect pages based on authentication
6. ✅ **Persistence** - Auth state survives page reloads
## 📁 Files Created
```
resources/js/
├── services/
│ └── authService.ts ← API service for auth endpoints
├── stores/
│ └── auth.ts ← Pinia store for auth state
├── middleware/
│ └── auth.ts ← Route guard middleware
├── composables/
│ └── useAuthGuard.ts ← Composables for protecting routes
└── components/
└── LoginExample.vue ← Example login component
```
## 🎯 How to Use
### 1. In Your Login Component
```vue
<script setup lang="ts">
import { useAuthStore } from '@/stores/auth';
import { router } from '@inertiajs/vue3';
import { ref } from 'vue';
const authStore = useAuthStore();
const email = ref('');
const password = ref('');
async function login() {
const success = await authStore.login({
email: email.value,
password: password.value,
remember: true
});
if (success) {
router.get('/dashboard');
}
}
</script>
<template>
<form @submit.prevent="login">
<input v-model="email" type="email" placeholder="Email" />
<input v-model="password" type="password" placeholder="Password" />
<button type="submit" :disabled="authStore.loading">
{{ authStore.loading ? 'Logging in...' : 'Login' }}
</button>
<p v-if="authStore.error" class="error">{{ authStore.error }}</p>
</form>
</template>
```
### 2. Protect Your Dashboard
```vue
<script setup lang="ts">
import { useAuthGuard } from '@/composables/useAuthGuard';
// This will redirect to /login if not authenticated
useAuthGuard();
</script>
<template>
<div>
<h1>Protected Dashboard</h1>
</div>
</template>
```
### 3. Display User Info
```vue
<script setup lang="ts">
import { useAuthStore } from '@/stores/auth';
const authStore = useAuthStore();
</script>
<template>
<div v-if="authStore.isAuthenticated">
Welcome, {{ authStore.currentUser?.name }}!
<button @click="authStore.logout">Logout</button>
</div>
</template>
```
## 🔧 Backend Requirements
You need these Laravel API endpoints:
### Login Endpoint
```php
// POST http://localhost:8000/api/auth/login
Route::post('api/auth/login', function(Request $request) {
$credentials = $request->validate([
'email' => 'required|email',
'password' => 'required',
]);
if (Auth::attempt($credentials)) {
return response()->json([
'user' => Auth::user(),
'token' => Auth::user()->createToken('auth')->plainTextToken
]);
}
return response()->json(['message' => 'Invalid credentials'], 401);
});
```
### User Endpoint
```php
// GET http://localhost:8000/api/auth/user
Route::get('api/auth/user', function() {
return response()->json(['user' => Auth::user()]);
})->middleware('auth:sanctum');
```
### Logout Endpoint
```php
// POST http://localhost:8000/api/auth/logout
Route::post('api/auth/logout', function(Request $request) {
$request->user()->currentAccessToken()->delete();
return response()->json(['message' => 'Logged out']);
})->middleware('auth:sanctum');
```
## 🎨 Complete Working Examples
### Example 1: Simple Login Page
```vue
<script setup lang="ts">
import { useAuthStore } from '@/stores/auth';
import { useGuestGuard } from '@/composables/useAuthGuard';
import { router } from '@inertiajs/vue3';
import { ref } from 'vue';
useGuestGuard(); // Redirect to dashboard if already logged in
const authStore = useAuthStore();
const form = ref({
email: '',
password: '',
remember: false
});
async function handleSubmit() {
const success = await authStore.login(form.value);
if (success) {
router.get('/dashboard');
}
}
</script>
<template>
<div class="login-container">
<h1>Login</h1>
<div v-if="authStore.error" class="alert alert-error">
{{ authStore.error }}
</div>
<form @submit.prevent="handleSubmit">
<div>
<label>Email</label>
<input v-model="form.email" type="email" required />
</div>
<div>
<label>Password</label>
<input v-model="form.password" type="password" required />
</div>
<div>
<label>
<input v-model="form.remember" type="checkbox" />
Remember me
</label>
</div>
<button type="submit" :disabled="authStore.loading">
{{ authStore.loading ? 'Logging in...' : 'Login' }}
</button>
</form>
</div>
</template>
```
### Example 2: Protected Dashboard
```vue
<script setup lang="ts">
import { useAuthGuard } from '@/composables/useAuthGuard';
import { useAuthStore } from '@/stores/auth';
import { router } from '@inertiajs/vue3';
useAuthGuard(); // Redirect to login if not authenticated
const authStore = useAuthStore();
async function handleLogout() {
await authStore.logout();
router.get('/login');
}
</script>
<template>
<div class="dashboard">
<header>
<h1>Dashboard</h1>
<div class="user-menu">
<span>{{ authStore.currentUser?.name }}</span>
<button @click="handleLogout">Logout</button>
</div>
</header>
<main>
<p>Welcome back, {{ authStore.currentUser?.name }}!</p>
<p>Email: {{ authStore.currentUser?.email }}</p>
</main>
</div>
</template>
```
## 📋 Auth Store API Reference
```typescript
const authStore = useAuthStore();
// State
authStore.user // User object or null
authStore.token // Auth token or null
authStore.loading // Boolean: loading state
authStore.error // String: error message or null
// Getters
authStore.isAuthenticated // Boolean: is user logged in?
authStore.currentUser // User object or null
// Actions
await authStore.login({ email, password, remember }) // Returns boolean
await authStore.logout() // Returns void
await authStore.checkAuth() // Returns boolean
await authStore.fetchUser() // Returns void
authStore.clearError() // Returns void
authStore.setUser(userData) // Returns void
```
## ✨ Key Features
- ✅ **Auto-persistence**: Auth state saved to localStorage
- ✅ **CSRF Protection**: Automatically handled
- ✅ **Error Handling**: Built-in error messages
- ✅ **Loading States**: Track async operations
- ✅ **TypeScript**: Full type safety
- ✅ **Inertia Compatible**: Works with your existing setup
## 🐛 Troubleshooting
### Issue: User not staying logged in
**Solution**: Make sure your Laravel API returns the user object on the `/api/auth/user` endpoint
### Issue: CSRF token mismatch
**Solution**: Your existing `resources/js/lib/http.ts` handles this automatically
### Issue: Redirect not working
**Solution**: Ensure you're using `router.get()` from `@inertiajs/vue3`
## 📚 Next Steps
1. Create your Laravel API endpoints (see examples above)
2. Update your login page component
3. Add `useAuthGuard()` to protected pages
4. Test the authentication flow
For detailed documentation, see `AUTH_SETUP.md`

View File

@ -7,6 +7,7 @@ import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
import type { DefineComponent } from 'vue'; import type { DefineComponent } from 'vue';
import { createApp, h } from 'vue'; import { createApp, h } from 'vue';
import { initializeTheme } from './composables/useAppearance'; import { initializeTheme } from './composables/useAppearance';
import { setupAuthGuards } from './middleware/auth';
const appName = import.meta.env.VITE_APP_NAME || 'Laravel'; const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
const pinia = createPinia(); const pinia = createPinia();
@ -16,10 +17,14 @@ createInertiaApp({
title: (title) => (title ? `${title} - ${appName}` : appName), title: (title) => (title ? `${title} - ${appName}` : appName),
resolve: (name) => resolvePageComponent(`./pages/${name}.vue`, import.meta.glob<DefineComponent>('./pages/**/*.vue')), resolve: (name) => resolvePageComponent(`./pages/${name}.vue`, import.meta.glob<DefineComponent>('./pages/**/*.vue')),
setup({ el, App, props, plugin }) { setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) }) const app = createApp({ render: () => h(App, props) })
.use(pinia) .use(pinia)
.use(plugin) .use(plugin);
.mount(el);
// Initialize auth guards after pinia is set up
setupAuthGuards();
app.mount(el);
}, },
progress: { progress: {
color: '#4B5563', color: '#4B5563',

View File

@ -0,0 +1,128 @@
<script setup lang="ts">
import { useAuthStore } from '@/stores/auth';
import { router } from '@inertiajs/vue3';
import { ref } from 'vue';
const authStore = useAuthStore();
const email = ref('');
const password = ref('');
const remember = ref(false);
async function handleLogin() {
const success = await authStore.login({
email: email.value,
password: password.value,
remember: remember.value,
});
if (success) {
// Redirect to dashboard on successful login
router.get('/dashboard');
}
}
</script>
<template>
<div class="login-form">
<h2>Login</h2>
<div v-if="authStore.error" class="error-message">
{{ authStore.error }}
</div>
<form @submit.prevent="handleLogin">
<div class="form-group">
<label for="email">Email:</label>
<input
id="email"
v-model="email"
type="email"
required
placeholder="email@example.com"
/>
</div>
<div class="form-group">
<label for="password">Password:</label>
<input
id="password"
v-model="password"
type="password"
required
placeholder="Password"
/>
</div>
<div class="form-group">
<label>
<input v-model="remember" type="checkbox" />
Remember me
</label>
</div>
<button type="submit" :disabled="authStore.loading">
{{ authStore.loading ? 'Logging in...' : 'Login' }}
</button>
</form>
<div v-if="authStore.isAuthenticated" class="user-info">
<p>Logged in as: {{ authStore.currentUser?.name }}</p>
<button @click="authStore.logout">Logout</button>
</div>
</div>
</template>
<style scoped>
.login-form {
max-width: 400px;
margin: 0 auto;
padding: 20px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
}
.form-group input[type='email'],
.form-group input[type='password'] {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
.error-message {
color: red;
margin-bottom: 15px;
padding: 10px;
background-color: #fee;
border-radius: 4px;
}
button {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.user-info {
margin-top: 20px;
padding: 15px;
background-color: #e7f3ff;
border-radius: 4px;
}
</style>

View File

@ -3,7 +3,7 @@
<!-- Background image with overlay --> <!-- Background image with overlay -->
<div class="absolute inset-0"> <div class="absolute inset-0">
<img src="/image005.jpg" alt="Background" class="h-full w-full object-cover" /> <img src="/image005.jpg" alt="Background" class="h-full w-full object-cover" />
<div class="absolute inset-0 bg-gradient-to-br from-[var(--c-purple)]/70 to-[var(--c-deep-navy)]/80"></div> <div class="absolute inset-0 bg-gradient-to-br from-[var(--c-purple)]/70 to-[var(--c-deep-navy)]/80" style="background: linear-gradient(135deg, rgba(76, 29, 149, 0.70), rgba(30, 27, 75, 0.80));"></div>
</div> </div>
<div class="relative z-10 mx-auto max-w-5xl px-4"> <div class="relative z-10 mx-auto max-w-5xl px-4">

View File

@ -1,5 +1,5 @@
<template> <template>
<section class="relative overflow-hidden bg-gradient-to-b from-[var(--c-purple)]/10 to-[var(--c-purple)]/5 py-20 sm:py-24"> <section class="relative overflow-hidden bg-gradient-to-b from-[var(--c-purple)]/10 to-[var(--c-purple)]/5 py-20 sm:py-24" style="background: linear-gradient(to bottom, rgba(76, 29, 149, 0.10), rgba(76, 29, 149, 0.05));">
<!-- Single elegant background image --> <!-- Single elegant background image -->
<div class="absolute inset-0"> <div class="absolute inset-0">
<div class="absolute inset-0 bg-[url('/image002.jpg')] bg-cover bg-center opacity-20 mix-blend-soft-light"></div> <div class="absolute inset-0 bg-[url('/image002.jpg')] bg-cover bg-center opacity-20 mix-blend-soft-light"></div>

View File

@ -1,5 +1,5 @@
<template> <template>
<section class="bg-gradient-to-b from-[var(--c-purple)]/10 to-[var(--c-purple)]/5 py-20 sm:py-24" id="testimonials"> <section class="bg-gradient-to-b from-[var(--c-purple)]/10 to-[var(--c-purple)]/5 py-20 sm:py-24" id="testimonials" style="background: linear-gradient(to bottom, rgba(76, 29, 149, 0.10), rgba(76, 29, 149, 0.05));">
<h2 class="mb-16 transform text-center text-4xl font-bold text-white transition-all duration-700 hover:scale-105 md:text-5xl"> <h2 class="mb-16 transform text-center text-4xl font-bold text-white transition-all duration-700 hover:scale-105 md:text-5xl">
Ce Que Nos Initiés Disent Ce Que Nos Initiés Disent
</h2> </h2>

View File

@ -0,0 +1,103 @@
import { useAuthStore } from '@/stores/auth';
import { router } from '@inertiajs/vue3';
import { computed, onMounted, ref } from 'vue';
/**
* Composable to protect pages that require authentication
* Usage: Call this in the setup of any component that requires auth
*
* Example:
* ```vue
* <script setup lang="ts">
* import { useAuthGuard } from '@/composables/useAuthGuard';
*
* const { isChecking } = useAuthGuard();
* </script>
* ```
*
* Note: With the global middleware setup in app.ts, this composable is optional.
* The global middleware will handle redirects automatically.
* Use this composable if you need to show loading states or access auth data.
*/
export function useAuthGuard(redirectTo: string = '/login') {
const authStore = useAuthStore();
const isChecking = ref(true);
onMounted(async () => {
const isAuthenticated = await authStore.checkAuth();
isChecking.value = false;
if (!isAuthenticated) {
router.get(redirectTo, {}, { replace: true });
}
});
return {
isAuthenticated: computed(() => authStore.isAuthenticated),
user: computed(() => authStore.currentUser),
loading: computed(() => authStore.loading),
isChecking,
};
}
/**
* Composable to protect pages that should only be accessible to guests
* (e.g., login, register pages)
*
* Example:
* ```vue
* <script setup lang="ts">
* import { useGuestGuard } from '@/composables/useAuthGuard';
*
* const { isChecking } = useGuestGuard();
* </script>
* ```
*
* Note: With the global middleware setup in app.ts, this composable is optional.
* The global middleware will handle redirects automatically.
* Use this composable if you need to show loading states.
*/
export function useGuestGuard(redirectTo: string = '/dashboard') {
const authStore = useAuthStore();
const isChecking = ref(true);
onMounted(async () => {
const isAuthenticated = await authStore.checkAuth();
isChecking.value = false;
if (isAuthenticated) {
router.get(redirectTo, {}, { replace: true });
}
});
return {
isAuthenticated: computed(() => authStore.isAuthenticated),
isChecking,
};
}
/**
* Simple composable to access auth state without guards
*
* Example:
* ```vue
* <script setup lang="ts">
* import { useAuth } from '@/composables/useAuthGuard';
*
* const { user, isAuthenticated, logout } = useAuth();
* </script>
* ```
*/
export function useAuth() {
const authStore = useAuthStore();
return {
user: computed(() => authStore.currentUser),
isAuthenticated: computed(() => authStore.isAuthenticated),
loading: computed(() => authStore.loading),
error: computed(() => authStore.error),
login: authStore.login,
logout: authStore.logout,
checkAuth: authStore.checkAuth,
};
}

View File

@ -24,6 +24,7 @@ onMounted(() => {
<template> <template>
<div <div
class="group/design-root relative flex size-full min-h-screen flex-col overflow-x-hidden bg-gradient-to-br from-[var(--c-deep-navy)] via-[var(--c-purple)] to-black" class="group/design-root relative flex size-full min-h-screen flex-col overflow-x-hidden bg-gradient-to-br from-[var(--c-deep-navy)] via-[var(--c-purple)] to-black"
style="background: linear-gradient(135deg, #1e1b4b, #4c1d95, #000000);"
> >
<div class="layout-container flex h-full grow flex-col"> <div class="layout-container flex h-full grow flex-col">
<!-- Enhanced Header with Bigger Font --> <!-- Enhanced Header with Bigger Font -->

View File

@ -0,0 +1,98 @@
import { useAuthStore } from '@/stores/auth';
import { router } from '@inertiajs/vue3';
/**
* Protected routes that require authentication
*/
const protectedRoutes = [
'/dashboard',
'/profile',
'/paiement',
'/cards',
'/agenda',
'/checkout',
'/payments',
];
/**
* Guest-only routes (redirect if authenticated)
*/
const guestRoutes = [
'/login',
'/register',
'/password/request',
'/password/reset',
];
/**
* Check if a URL matches any of the route patterns
*/
function matchesRoute(url: string, routes: string[]): boolean {
return routes.some(route => url.startsWith(route));
}
/**
* Setup Inertia.js navigation guards
* This uses Inertia's event system to intercept navigation
*/
export function setupAuthGuards(): void {
const authStore = useAuthStore();
// Initialize auth state on app load
authStore.checkAuth().catch(() => {
// Silent fail - user is not authenticated
});
// Intercept Inertia navigation before it happens
router.on('before', async (event) => {
const url = event.detail.visit.url.pathname;
// Check if trying to access a protected route
if (matchesRoute(url, protectedRoutes)) {
const isAuthenticated = await authStore.checkAuth();
if (!isAuthenticated && url !== '/login') {
// Prevent the navigation and redirect to login
event.preventDefault();
router.get('/login', {}, {
preserveState: false,
replace: true
});
return;
}
}
// Check if trying to access a guest-only route
if (matchesRoute(url, guestRoutes)) {
const isAuthenticated = await authStore.checkAuth();
if (isAuthenticated && url !== '/dashboard') {
// Prevent the navigation and redirect to dashboard
event.preventDefault();
router.get('/dashboard', {}, {
preserveState: false,
replace: true
});
return;
}
}
});
}
/**
* Add a protected route to the list
*/
export function addProtectedRoute(route: string): void {
if (!protectedRoutes.includes(route)) {
protectedRoutes.push(route);
}
}
/**
* Add a guest route to the list
*/
export function addGuestRoute(route: string): void {
if (!guestRoutes.includes(route)) {
guestRoutes.push(route);
}
}

View File

@ -0,0 +1,104 @@
import http from '@/lib/http';
export interface LoginCredentials {
email: string;
password: string;
remember?: boolean;
}
export interface User {
id: number;
name: string;
email: string;
email_verified_at?: string;
created_at: string;
updated_at: string;
}
export interface LoginResponse {
user: User;
token?: string;
message?: string;
}
export interface ApiError {
message: string;
errors?: Record<string, string[]>;
}
class AuthService {
/**
* Login user with email and password
*/
async login(credentials: LoginCredentials): Promise<LoginResponse> {
try {
const response = await http.post<LoginResponse>(
'http://localhost:8000/api/auth/login',
credentials
);
return response.data;
} catch (error: any) {
throw this.handleError(error);
}
}
/**
* Logout the current user
*/
async logout(): Promise<void> {
try {
await http.post('http://localhost:8000/api/auth/logout');
} catch (error: any) {
throw this.handleError(error);
}
}
/**
* Get the current authenticated user
*/
async getCurrentUser(): Promise<User> {
try {
const response = await http.get<{ user: User }>(
'http://localhost:8000/api/auth/user'
);
return response.data.user;
} catch (error: any) {
throw this.handleError(error);
}
}
/**
* Check if user is authenticated
*/
async checkAuth(): Promise<boolean> {
try {
await this.getCurrentUser();
return true;
} catch {
return false;
}
}
/**
* Handle API errors
*/
private handleError(error: any): Error {
if (error.response?.data) {
const apiError = error.response.data as ApiError;
const message = apiError.message || 'An error occurred';
const errors = apiError.errors;
if (errors) {
const firstError = Object.values(errors)[0];
return new Error(firstError ? firstError[0] : message);
}
return new Error(message);
}
return new Error(error.message || 'Network error occurred');
}
}
export const authService = new AuthService();
export default authService;

125
resources/js/stores/auth.ts Normal file
View File

@ -0,0 +1,125 @@
import authService, { type LoginCredentials, type User } from '@/services/authService';
import { defineStore } from 'pinia';
import { computed, ref } from 'vue';
export const useAuthStore = defineStore(
'auth',
() => {
// State
const user = ref<User | null>(null);
const token = ref<string | null>(null);
const loading = ref(false);
const error = ref<string | null>(null);
// Getters
const isAuthenticated = computed(() => user.value !== null);
const currentUser = computed(() => user.value);
// Actions
async function login(credentials: LoginCredentials): Promise<boolean> {
loading.value = true;
error.value = null;
try {
const response = await authService.login(credentials);
user.value = response.user;
if (response.token) {
token.value = response.token;
}
return true;
} catch (err: any) {
error.value = err.message || 'Login failed';
return false;
} finally {
loading.value = false;
}
}
async function logout(): Promise<void> {
loading.value = true;
try {
await authService.logout();
} catch (err: any) {
console.error('Logout error:', err);
} finally {
// Clear state regardless of API call result
user.value = null;
token.value = null;
error.value = null;
loading.value = false;
}
}
async function checkAuth(): Promise<boolean> {
if (user.value) {
return true;
}
loading.value = true;
try {
const currentUser = await authService.getCurrentUser();
user.value = currentUser;
return true;
} catch {
user.value = null;
token.value = null;
return false;
} finally {
loading.value = false;
}
}
async function fetchUser(): Promise<void> {
loading.value = true;
error.value = null;
try {
const currentUser = await authService.getCurrentUser();
user.value = currentUser;
} catch (err: any) {
error.value = err.message || 'Failed to fetch user';
user.value = null;
token.value = null;
} finally {
loading.value = false;
}
}
function clearError(): void {
error.value = null;
}
function setUser(userData: User): void {
user.value = userData;
}
return {
// State
user,
token,
loading,
error,
// Getters
isAuthenticated,
currentUser,
// Actions
login,
logout,
checkAuth,
fetchUser,
clearError,
setUser,
};
},
{
persist: {
key: 'auth-storage',
storage: typeof window !== 'undefined' ? localStorage : undefined,
paths: ['user', 'token'],
},
}
);