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 { createApp, h } from 'vue';
|
||||
import { initializeTheme } from './composables/useAppearance';
|
||||
import { setupAuthGuards } from './middleware/auth';
|
||||
|
||||
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
|
||||
const pinia = createPinia();
|
||||
@ -16,10 +17,14 @@ createInertiaApp({
|
||||
title: (title) => (title ? `${title} - ${appName}` : appName),
|
||||
resolve: (name) => resolvePageComponent(`./pages/${name}.vue`, import.meta.glob<DefineComponent>('./pages/**/*.vue')),
|
||||
setup({ el, App, props, plugin }) {
|
||||
createApp({ render: () => h(App, props) })
|
||||
const app = createApp({ render: () => h(App, props) })
|
||||
.use(pinia)
|
||||
.use(plugin)
|
||||
.mount(el);
|
||||
.use(plugin);
|
||||
|
||||
// Initialize auth guards after pinia is set up
|
||||
setupAuthGuards();
|
||||
|
||||
app.mount(el);
|
||||
},
|
||||
progress: {
|
||||
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 -->
|
||||
<div class="absolute inset-0">
|
||||
<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 class="relative z-10 mx-auto max-w-5xl px-4">
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<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 -->
|
||||
<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>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<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">
|
||||
Ce Que Nos Initiés Disent
|
||||
</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>
|
||||
<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"
|
||||
style="background: linear-gradient(135deg, #1e1b4b, #4c1d95, #000000);"
|
||||
>
|
||||
<div class="layout-container flex h-full grow flex-col">
|
||||
<!-- 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