admin without rights
This commit is contained in:
65
app/Http/Controllers/AuthController.php
Normal file
65
app/Http/Controllers/AuthController.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
// Регистрация нового пользователя
|
||||
public function register(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|string|min:6',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'message' => 'Ошибка валидации',
|
||||
'errors' => $validator->errors()
|
||||
], 422);
|
||||
}
|
||||
|
||||
$user = \App\Models\User::create([
|
||||
'name' => $request->name,
|
||||
'email' => $request->email,
|
||||
'password' => bcrypt($request->password),
|
||||
'phone' => $request->phone ?? null,
|
||||
'role' => 'client', // по умолчанию клиент
|
||||
]);
|
||||
|
||||
// Создаём токен для Sanctum
|
||||
$token = $user->createToken('main-token')->plainTextToken;
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Пользователь создан',
|
||||
'user' => $user,
|
||||
'token' => $token
|
||||
], 201);
|
||||
}
|
||||
|
||||
// Вход
|
||||
public function login(Request $request)
|
||||
{
|
||||
$credentials = $request->only('email', 'password');
|
||||
|
||||
if (!Auth::attempt($credentials)) {
|
||||
return response()->json([
|
||||
'message' => 'Неверный email или пароль'
|
||||
], 401);
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
$token = $user->createToken('main-token')->plainTextToken;
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Успешный вход',
|
||||
'user' => $user,
|
||||
'token' => $token
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ return new class extends Migration
|
||||
public function up(): void
|
||||
{
|
||||
//таблица user
|
||||
Schema::create('user', function (Blueprint $table) {
|
||||
Schema::create('users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
|
||||
@@ -29,6 +29,7 @@ services:
|
||||
####################################################################################################
|
||||
db:
|
||||
image: mysql:8.1
|
||||
command: --default-authentication-plugin=mysql_native_password
|
||||
ports:
|
||||
- 3306:3306
|
||||
volumes:
|
||||
|
||||
@@ -171,260 +171,21 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let token = localStorage.getItem('token');
|
||||
let selectedEmployeeId = null;
|
||||
let selectedDate = null;
|
||||
let currentMonth = new Date().getMonth();
|
||||
let currentYear = new Date().getFullYear();
|
||||
let employees = [];
|
||||
let intervals = [];
|
||||
const token = localStorage.getItem('auth_token');
|
||||
if (!token) {
|
||||
window.location.href = '/register-login.html';
|
||||
}
|
||||
|
||||
// Проверка авторизации
|
||||
if (!token) {
|
||||
window.location.href = 'register-login.html';
|
||||
}
|
||||
|
||||
// Загрузка сотрудников при старте
|
||||
window.onload = async function() {
|
||||
await loadEmployees();
|
||||
};
|
||||
|
||||
// Загрузить список сотрудников
|
||||
async function loadEmployees() {
|
||||
try {
|
||||
const response = await fetch('/api/admin/users?role=employee', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const allUsers = await response.json();
|
||||
employees = allUsers.filter(user => user.role === 'employee' || user.role === 'admin');
|
||||
|
||||
const select = document.getElementById('employeeSelect');
|
||||
select.innerHTML = '<option value="">— Выберите сотрудника —</option>';
|
||||
|
||||
for (let employee of employees) {
|
||||
select.innerHTML += `<option value="${employee.id}">${employee.name}</option>`;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Ошибка загрузки сотрудников');
|
||||
}
|
||||
}
|
||||
|
||||
// При смене сотрудника
|
||||
function loadEmployeeSchedule() {
|
||||
const select = document.getElementById('employeeSelect');
|
||||
selectedEmployeeId = select.value;
|
||||
|
||||
if (selectedEmployeeId) {
|
||||
document.getElementById('employeeInfo').style.display = 'block';
|
||||
document.getElementById('selectedEmployeeId').textContent = selectedEmployeeId;
|
||||
document.getElementById('selectedEmployeeName').textContent = select.options[select.selectedIndex].text;
|
||||
document.getElementById('calendarSection').style.display = 'block';
|
||||
renderCalendar();
|
||||
} else {
|
||||
document.getElementById('calendarSection').style.display = 'none';
|
||||
document.getElementById('timeIntervalsSection').style.display = 'none';
|
||||
document.getElementById('employeeInfo').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Простой календарь
|
||||
function renderCalendar() {
|
||||
const calendarDays = document.getElementById('calendarDays');
|
||||
const monthYear = document.getElementById('monthYear');
|
||||
|
||||
monthYear.textContent = new Date(currentYear, currentMonth).toLocaleDateString('ru', {
|
||||
year: 'numeric', month: 'long'
|
||||
});
|
||||
|
||||
calendarDays.innerHTML = '';
|
||||
const firstDay = new Date(currentYear, currentMonth, 1).getDay();
|
||||
const daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate();
|
||||
|
||||
let dayCounter = 1 - firstDay + 1;
|
||||
|
||||
for (let i = 0; i < 42; i++) {
|
||||
const day = document.createElement('div');
|
||||
day.className = 'calendar-day';
|
||||
|
||||
if (dayCounter < 1) {
|
||||
day.textContent = new Date(currentYear, currentMonth, 0).getDate() + dayCounter;
|
||||
day.classList.add('other-month');
|
||||
dayCounter++;
|
||||
} else if (dayCounter <= daysInMonth) {
|
||||
const dateStr = `${currentYear}-${String(currentMonth + 1).padStart(2, '0')}-${String(dayCounter).padStart(2, '0')}`;
|
||||
day.textContent = dayCounter;
|
||||
day.onclick = () => selectDate(dateStr);
|
||||
dayCounter++;
|
||||
} else {
|
||||
day.textContent = dayCounter - daysInMonth;
|
||||
day.classList.add('other-month');
|
||||
dayCounter++;
|
||||
}
|
||||
|
||||
calendarDays.appendChild(day);
|
||||
}
|
||||
}
|
||||
|
||||
function selectDate(date) {
|
||||
selectedDate = date;
|
||||
document.querySelectorAll('.calendar-day').forEach(day => day.classList.remove('selected'));
|
||||
event.target.classList.add('selected');
|
||||
|
||||
document.getElementById('selectedDateTitle').textContent = `Интервалы на ${new Date(date).toLocaleDateString('ru')}`;
|
||||
loadIntervals();
|
||||
}
|
||||
|
||||
function prevMonth() {
|
||||
currentMonth--;
|
||||
if (currentMonth < 0) {
|
||||
currentMonth = 11;
|
||||
currentYear--;
|
||||
}
|
||||
renderCalendar();
|
||||
}
|
||||
|
||||
function nextMonth() {
|
||||
currentMonth++;
|
||||
if (currentMonth > 11) {
|
||||
currentMonth = 0;
|
||||
currentYear++;
|
||||
}
|
||||
renderCalendar();
|
||||
}
|
||||
|
||||
// Загрузить интервалы для даты
|
||||
async function loadIntervals() {
|
||||
try {
|
||||
document.getElementById('timeIntervalsSection').style.display = 'block';
|
||||
const response = await fetch(`/api/admin/availabilities?employee_id=${selectedEmployeeId}&date=${selectedDate}`, {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
|
||||
intervals = await response.json();
|
||||
renderIntervals();
|
||||
} catch (error) {
|
||||
intervals = [];
|
||||
renderIntervals();
|
||||
}
|
||||
}
|
||||
|
||||
// Отобразить интервалы
|
||||
function renderIntervals() {
|
||||
const container = document.getElementById('intervalsList');
|
||||
|
||||
if (intervals.length === 0) {
|
||||
container.innerHTML = '<div class="no-intervals">Нет интервалов на эту дату</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = '';
|
||||
|
||||
for (let interval of intervals) {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'interval-item';
|
||||
item.innerHTML = `
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<strong>${interval.starttime.slice(0,5)} - ${interval.endtime.slice(0,5)}</strong>
|
||||
<div>
|
||||
<label style="margin-right: 15px;">
|
||||
<input type="checkbox" ${interval.isavailable ? '' : 'checked'} onchange="updateInterval(${interval.id}, this.checked)">
|
||||
Недоступен
|
||||
</label>
|
||||
<button class="delete-interval" onclick="deleteInterval(${interval.id})" title="Удалить">×</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Добавить интервал
|
||||
async function addInterval(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const startTime = document.getElementById('startTime').value;
|
||||
const endTime = document.getElementById('endTime').value;
|
||||
const isUnavailable = document.getElementById('isUnavailable').checked;
|
||||
|
||||
if (!startTime || !endTime) {
|
||||
alert('Заполните время начала и окончания');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/admin/availabilities', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
employee_id: selectedEmployeeId,
|
||||
date: selectedDate,
|
||||
starttime: startTime + ':00',
|
||||
endtime: endTime + ':00',
|
||||
isavailable: !isUnavailable
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
document.getElementById('startTime').value = '';
|
||||
document.getElementById('endTime').value = '';
|
||||
document.getElementById('isUnavailable').checked = false;
|
||||
loadIntervals();
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Ошибка добавления интервала');
|
||||
}
|
||||
}
|
||||
|
||||
// Обновить доступность
|
||||
async function updateInterval(id, isAvailable) {
|
||||
try {
|
||||
const response = await fetch(`/api/admin/availabilities/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({ isavailable: isAvailable })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
alert('Ошибка обновления');
|
||||
loadIntervals(); // Вернуть как было
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Ошибка сети');
|
||||
loadIntervals();
|
||||
}
|
||||
}
|
||||
|
||||
// Удалить интервал
|
||||
async function deleteInterval(id) {
|
||||
if (confirm('Удалить интервал?')) {
|
||||
try {
|
||||
const response = await fetch(`/api/admin/availabilities/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
loadIntervals();
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Ошибка удаления');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function logout() {
|
||||
localStorage.removeItem('token');
|
||||
window.location.href = 'index.html';
|
||||
}
|
||||
fetch('/api/admin/availabilities', {
|
||||
headers: { 'Authorization': 'Bearer ' + token }
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
const list = document.getElementById('schedule-list');
|
||||
data.forEach(avail => {
|
||||
list.innerHTML += `<p>${avail.employee_id} — ${avail.date} ${avail.starttime} - ${avail.endtime}</p>`;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -180,176 +180,45 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let token = localStorage.getItem('token');
|
||||
let services = [];
|
||||
let selectedServiceId = null;
|
||||
// Получаем токен из localStorage
|
||||
const token = localStorage.getItem('auth_token');
|
||||
|
||||
// Проверка авторизации и роли
|
||||
if (!token) {
|
||||
window.location.href = 'register-login.html';
|
||||
// Если нет токена — перенаправляем на вход
|
||||
if (!token) {
|
||||
alert('Доступ только для администраторов!');
|
||||
window.location.href = '/register-login.html';
|
||||
}
|
||||
|
||||
// Загружаем услуги
|
||||
fetch('/api/admin/services', {
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
|
||||
// Загрузка услуг при загрузке страницы
|
||||
window.onload = loadServices;
|
||||
|
||||
// Загрузить услуги из API
|
||||
async function loadServices() {
|
||||
try {
|
||||
const response = await fetch('/api/admin/services', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
services = await response.json();
|
||||
renderServices();
|
||||
} else {
|
||||
alert('Ошибка доступа или загрузки услуг');
|
||||
window.location.href = 'register-login.html';
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Ошибка сети');
|
||||
}
|
||||
}
|
||||
|
||||
// Отобразить услуги в таблице
|
||||
function renderServices() {
|
||||
const tbody = document.getElementById('servicesTable');
|
||||
|
||||
if (services.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="7" style="text-align: center; padding: 40px; color: #666;">Нет услуг</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = '';
|
||||
|
||||
// Простой цикл для каждой услуги
|
||||
for (let service of services) {
|
||||
const row = document.createElement('tr');
|
||||
const statusClass = service.isactive ? 'active' : 'inactive';
|
||||
|
||||
row.innerHTML = `
|
||||
<td>${service.id}</td>
|
||||
<td><strong>${service.name}</strong></td>
|
||||
<td>${service.description || '—'}</td>
|
||||
<td>${service.durationminutes} мин</td>
|
||||
<td>${service.price}₽</td>
|
||||
<td><span class="status-badge status-${statusClass}">${service.isactive ? 'Активна' : 'Неактивна'}</span></td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<button class="btn-small btn-edit" onclick="editService(${service.id})">
|
||||
✏️ Редактировать
|
||||
</button>
|
||||
<button class="btn-small btn-toggle" onclick="toggleService(${service.id}, ${service.isactive})">
|
||||
${service.isactive ? '❌ Деактивировать' : '✅ Активировать'}
|
||||
</button>
|
||||
<button class="btn-small btn-delete" onclick="showDeleteModal(${service.id}, '${service.name}')">
|
||||
🗑️ Удалить
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
`;
|
||||
|
||||
tbody.appendChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
// Добавить новую услугу
|
||||
async function addService(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const formData = {
|
||||
name: document.getElementById('serviceName').value,
|
||||
description: document.getElementById('serviceDescription').value,
|
||||
durationminutes: parseInt(document.getElementById('serviceDuration').value),
|
||||
price: parseInt(document.getElementById('servicePrice').value),
|
||||
isactive: true
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/admin/services', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify(formData)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert('✅ Услуга создана!');
|
||||
document.getElementById('addServiceForm').reset();
|
||||
loadServices(); // Перезагрузить таблицу
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert('Ошибка: ' + error.error || error.message);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Ошибка сети');
|
||||
}
|
||||
}
|
||||
|
||||
// Переключить статус (активна/неактивна)
|
||||
async function toggleService(id, isActive) {
|
||||
try {
|
||||
const response = await fetch(`/api/admin/services/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({ isactive: !isActive })
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert(`✅ Услуга ${!isActive ? 'активирована' : 'деактивирована'}`);
|
||||
loadServices();
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Ошибка сети');
|
||||
}
|
||||
}
|
||||
|
||||
// Показать модальное окно удаления
|
||||
function showDeleteModal(id, name) {
|
||||
selectedServiceId = id;
|
||||
document.getElementById('deleteServiceName').textContent = name;
|
||||
document.getElementById('deleteModal').style.display = 'block';
|
||||
}
|
||||
|
||||
// Удалить услугу
|
||||
async function confirmDelete() {
|
||||
try {
|
||||
const response = await fetch(`/api/admin/services/${selectedServiceId}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert('✅ Услуга удалена');
|
||||
closeModal();
|
||||
loadServices();
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Ошибка сети');
|
||||
}
|
||||
}
|
||||
|
||||
// Закрыть модальное окно
|
||||
function closeModal() {
|
||||
document.getElementById('deleteModal').style.display = 'none';
|
||||
}
|
||||
|
||||
// Выход
|
||||
function logout() {
|
||||
localStorage.removeItem('token');
|
||||
window.location.href = 'index.html';
|
||||
}
|
||||
|
||||
// Закрытие модального окна по клику вне области
|
||||
window.onclick = function(event) {
|
||||
const modal = document.getElementById('deleteModal');
|
||||
if (event.target === modal) closeModal();
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Ошибка сервера: ' + response.status);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
const servicesList = document.getElementById('services-list');
|
||||
data.forEach(service => {
|
||||
const item = document.createElement('div');
|
||||
item.innerHTML = `
|
||||
<h3>${service.name}</h3>
|
||||
<p>Цена: ${service.price} ₽</p>
|
||||
<p>Описание: ${service.description || 'Нет описания'}</p>
|
||||
<hr>
|
||||
`;
|
||||
servicesList.appendChild(item);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Ошибка:', error);
|
||||
document.body.innerHTML = `<h2 style="color: red;">Ошибка загрузки данных: ${error.message}</h2>`;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -93,7 +93,6 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Хедер -->
|
||||
<header>
|
||||
<div class="header-content">
|
||||
<div class="logo">🧹 КлинСервис</div>
|
||||
@@ -104,7 +103,6 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Hero секция -->
|
||||
<section class="hero">
|
||||
<div class="hero-content">
|
||||
<h1>Профессиональная уборка</h1>
|
||||
@@ -116,7 +114,6 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Услуги -->
|
||||
<section class="services" id="services">
|
||||
<h2 class="section-title">Наши услуги</h2>
|
||||
<div class="services-grid">
|
||||
@@ -141,7 +138,6 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Футер -->
|
||||
<footer>
|
||||
<div class="footer-content">
|
||||
<h3>🧹 КлинСервис</h3>
|
||||
@@ -156,7 +152,7 @@
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
// Простая прокрутка к секции услуг
|
||||
// прокрутка к секции услуг
|
||||
document.querySelector('.hero-buttons .btn-secondary').onclick = function(e) {
|
||||
e.preventDefault();
|
||||
document.getElementById('services').scrollIntoView({
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Contracts\Http\Kernel;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
define('LARAVEL_START', microtime(true));
|
||||
|
||||
// Determine if the application is in maintenance mode...
|
||||
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
|
||||
require $maintenance;
|
||||
}
|
||||
|
||||
// Register the Composer autoloader...
|
||||
require __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
// Bootstrap Laravel and handle the request...
|
||||
/** @var Application $app */
|
||||
$app = require_once __DIR__.'/../bootstrap/app.php';
|
||||
|
||||
$app->handleRequest(Request::capture());
|
||||
$kernel = $app->make(Kernel::class);
|
||||
|
||||
$response = $kernel->handle(
|
||||
$request = Request::capture()
|
||||
)->send();
|
||||
|
||||
$kernel->terminate($request, $response);
|
||||
|
||||
223
public/register-login.html
Normal file
223
public/register-login.html
Normal file
@@ -0,0 +1,223 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Вход и регистрация</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f4f4f4;
|
||||
padding: 20px;
|
||||
}
|
||||
.container {
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
h2 {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background-color: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #5a67d8;
|
||||
}
|
||||
.message {
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.error {
|
||||
background-color: #ffebee;
|
||||
color: #c62828;
|
||||
}
|
||||
.success {
|
||||
background-color: #e8f5e9;
|
||||
color: #2e7d32;
|
||||
}
|
||||
.toggle {
|
||||
text-align: center;
|
||||
margin-top: 15px;
|
||||
color: #667eea;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<!-- Форма входа -->
|
||||
<div id="loginSection">
|
||||
<h2>Вход</h2>
|
||||
<div id="loginResult"></div>
|
||||
<div class="form-group">
|
||||
<label for="emailLogin">Email:</label>
|
||||
<input type="email" id="emailLogin" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="passwordLogin">Пароль:</label>
|
||||
<input type="password" id="passwordLogin" required>
|
||||
</div>
|
||||
<button onclick="loginUser()">Войти</button>
|
||||
<div class="toggle" onclick="showRegisterForm()">Нет аккаунта? Зарегистрироваться</div>
|
||||
</div>
|
||||
|
||||
<!-- Форма регистрации -->
|
||||
<div id="registerSection" style="display: none;">
|
||||
<h2>Регистрация</h2>
|
||||
<div id="registerResult"></div>
|
||||
<div class="form-group">
|
||||
<label for="nameReg">Имя:</label>
|
||||
<input type="text" id="nameReg" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="emailReg">Email:</label>
|
||||
<input type="email" id="emailReg" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="passwordReg">Пароль:</label>
|
||||
<input type="password" id="passwordReg" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="phoneReg">Телефон (не обязательно):</label>
|
||||
<input type="tel" id="phoneReg">
|
||||
</div>
|
||||
<button onclick="registerUser()">Зарегистрироваться</button>
|
||||
<div class="toggle" onclick="showLoginForm()">Уже есть аккаунт? Войти</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Показать форму регистрации
|
||||
function showRegisterForm() {
|
||||
document.getElementById('loginSection').style.display = 'none';
|
||||
document.getElementById('registerSection').style.display = 'block';
|
||||
}
|
||||
|
||||
// Показать форму входа
|
||||
function showLoginForm() {
|
||||
document.getElementById('registerSection').style.display = 'none';
|
||||
document.getElementById('loginSection').style.display = 'block';
|
||||
}
|
||||
|
||||
// Функция регистрации
|
||||
function registerUser() {
|
||||
// Собираем данные из формы
|
||||
var name = document.getElementById('nameReg').value;
|
||||
var email = document.getElementById('emailReg').value;
|
||||
var password = document.getElementById('passwordReg').value;
|
||||
var phone = document.getElementById('phoneReg').value || null;
|
||||
|
||||
// Подготавливаем данные для отправки
|
||||
var data = {
|
||||
name: name,
|
||||
email: email,
|
||||
password: password,
|
||||
phone: phone
|
||||
};
|
||||
|
||||
// Отправляем POST-запрос на сервер
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "/api/register", true);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
var resultDiv = document.getElementById('registerResult');
|
||||
if (xhr.status === 200 || xhr.status === 201) {
|
||||
resultDiv.className = "message success";
|
||||
resultDiv.innerHTML = "Регистрация прошла успешно! Теперь войдите.";
|
||||
// Через 2 секунды переключим на форму входа
|
||||
setTimeout(function() {
|
||||
showLoginForm();
|
||||
resultDiv.innerHTML = "";
|
||||
}, 2000);
|
||||
} else {
|
||||
resultDiv.className = "message error";
|
||||
try {
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
resultDiv.innerHTML = response.message || "Ошибка при регистрации";
|
||||
} catch (e) {
|
||||
resultDiv.innerHTML = "Неизвестная ошибка сервера";
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
xhr.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
// Функция входа
|
||||
function loginUser() {
|
||||
var email = document.getElementById('emailLogin').value;
|
||||
var password = document.getElementById('passwordLogin').value;
|
||||
|
||||
var data = {
|
||||
email: email,
|
||||
password: password
|
||||
};
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "/api/login", true);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
var resultDiv = document.getElementById('loginResult');
|
||||
if (xhr.status === 200) {
|
||||
resultDiv.className = "message success";
|
||||
resultDiv.innerHTML = "Вход выполнен!";
|
||||
// Сохраняем токен в localStorage (чтобы потом использовать)
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
localStorage.setItem('auth_token', response.token);
|
||||
// Перенаправляем на главную
|
||||
setTimeout(function() {
|
||||
window.location.href = "/";
|
||||
}, 1000);
|
||||
} else {
|
||||
resultDiv.className = "message error";
|
||||
try {
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
resultDiv.innerHTML = response.message || "Неверный email или пароль";
|
||||
} catch (e) {
|
||||
resultDiv.innerHTML = "Ошибка при входе";
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
xhr.send(JSON.stringify(data));
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -7,6 +7,7 @@ use App\Http\Controllers\AvailabilitiesController;
|
||||
use App\Http\Controllers\CategoriesController;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\AuthController;
|
||||
|
||||
Route::get('/user', function (Request $request) {
|
||||
return $request->user();
|
||||
@@ -15,6 +16,8 @@ Route::get('/user', function (Request $request) {
|
||||
// РЕГИСТРАЦИЯ ТОЛЬКО КЛИЕНТОВ (публичный)
|
||||
Route::post('/register', [UserController::class, 'register']);
|
||||
|
||||
Route::post('/login', [AuthController::class, 'login']);
|
||||
|
||||
// Существующие роуты categories
|
||||
Route::get('/categories', [CategoriesController::class, 'index'])->middleware('auth:sanctum');
|
||||
Route::get('/categories/{id}', [CategoriesController::class, 'show']);
|
||||
@@ -24,7 +27,7 @@ Route::post('/categories', [CategoriesController::class, 'create']);
|
||||
Route::get('/availability', [AvailabilitiesController::class, 'publicAvailability']);
|
||||
|
||||
// КЛИЕНТСКИЕ РОУТЫ БРОНИРОВАНИЙ (auth:sanctum)
|
||||
Route::middleware('auth:sanctum')->group(function () {
|
||||
Route::middleware('auth:sanctum', 'role:admin')->group(function () {
|
||||
Route::post('/bookings', [BookingsController::class, 'store']);
|
||||
Route::post('/bookings/{id}/cancel', [BookingsController::class, 'cancel']);
|
||||
Route::post('/bookings/{id}/cancel', [BookingsController::class, 'adminCancel']);
|
||||
|
||||
Reference in New Issue
Block a user