fix safari
This commit is contained in:
parent
4f7e5f78d5
commit
eec6c974c9
328
AUTH_SETUP.md
Normal file
328
AUTH_SETUP.md
Normal 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
293
QUICK_START_AUTH.md
Normal 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`
|
||||||
@ -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',
|
||||||
|
|||||||
128
resources/js/components/LoginExample.vue
Normal file
128
resources/js/components/LoginExample.vue
Normal 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>
|
||||||
@ -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">
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
103
resources/js/composables/useAuthGuard.ts
Normal file
103
resources/js/composables/useAuthGuard.ts
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -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 -->
|
||||||
|
|||||||
98
resources/js/middleware/auth.ts
Normal file
98
resources/js/middleware/auth.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
104
resources/js/services/authService.ts
Normal file
104
resources/js/services/authService.ts
Normal 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
125
resources/js/stores/auth.ts
Normal 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'],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
Loading…
x
Reference in New Issue
Block a user