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
|
public function up(): void
|
||||||
{
|
{
|
||||||
//таблица user
|
//таблица user
|
||||||
Schema::create('user', function (Blueprint $table) {
|
Schema::create('users', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('name');
|
$table->string('name');
|
||||||
$table->string('email')->unique();
|
$table->string('email')->unique();
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ services:
|
|||||||
####################################################################################################
|
####################################################################################################
|
||||||
db:
|
db:
|
||||||
image: mysql:8.1
|
image: mysql:8.1
|
||||||
|
command: --default-authentication-plugin=mysql_native_password
|
||||||
ports:
|
ports:
|
||||||
- 3306:3306
|
- 3306:3306
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -171,260 +171,21 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let token = localStorage.getItem('token');
|
const token = localStorage.getItem('auth_token');
|
||||||
let selectedEmployeeId = null;
|
if (!token) {
|
||||||
let selectedDate = null;
|
window.location.href = '/register-login.html';
|
||||||
let currentMonth = new Date().getMonth();
|
}
|
||||||
let currentYear = new Date().getFullYear();
|
|
||||||
let employees = [];
|
|
||||||
let intervals = [];
|
|
||||||
|
|
||||||
// Проверка авторизации
|
fetch('/api/admin/availabilities', {
|
||||||
if (!token) {
|
headers: { 'Authorization': 'Bearer ' + token }
|
||||||
window.location.href = 'register-login.html';
|
})
|
||||||
}
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
// Загрузка сотрудников при старте
|
const list = document.getElementById('schedule-list');
|
||||||
window.onload = async function() {
|
data.forEach(avail => {
|
||||||
await loadEmployees();
|
list.innerHTML += `<p>${avail.employee_id} — ${avail.date} ${avail.starttime} - ${avail.endtime}</p>`;
|
||||||
};
|
});
|
||||||
|
});
|
||||||
// Загрузить список сотрудников
|
|
||||||
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';
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -180,176 +180,45 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let token = localStorage.getItem('token');
|
// Получаем токен из localStorage
|
||||||
let services = [];
|
const token = localStorage.getItem('auth_token');
|
||||||
let selectedServiceId = null;
|
|
||||||
|
|
||||||
// Проверка авторизации и роли
|
// Если нет токена — перенаправляем на вход
|
||||||
if (!token) {
|
if (!token) {
|
||||||
window.location.href = 'register-login.html';
|
alert('Доступ только для администраторов!');
|
||||||
|
window.location.href = '/register-login.html';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Загружаем услуги
|
||||||
|
fetch('/api/admin/services', {
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + token,
|
||||||
|
'Accept': 'application/json'
|
||||||
}
|
}
|
||||||
|
})
|
||||||
// Загрузка услуг при загрузке страницы
|
.then(response => {
|
||||||
window.onload = loadServices;
|
if (!response.ok) {
|
||||||
|
throw new Error('Ошибка сервера: ' + response.status);
|
||||||
// Загрузить услуги из 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();
|
|
||||||
}
|
}
|
||||||
|
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>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -93,7 +93,6 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Хедер -->
|
|
||||||
<header>
|
<header>
|
||||||
<div class="header-content">
|
<div class="header-content">
|
||||||
<div class="logo">🧹 КлинСервис</div>
|
<div class="logo">🧹 КлинСервис</div>
|
||||||
@@ -104,7 +103,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- Hero секция -->
|
|
||||||
<section class="hero">
|
<section class="hero">
|
||||||
<div class="hero-content">
|
<div class="hero-content">
|
||||||
<h1>Профессиональная уборка</h1>
|
<h1>Профессиональная уборка</h1>
|
||||||
@@ -116,7 +114,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Услуги -->
|
|
||||||
<section class="services" id="services">
|
<section class="services" id="services">
|
||||||
<h2 class="section-title">Наши услуги</h2>
|
<h2 class="section-title">Наши услуги</h2>
|
||||||
<div class="services-grid">
|
<div class="services-grid">
|
||||||
@@ -141,7 +138,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Футер -->
|
|
||||||
<footer>
|
<footer>
|
||||||
<div class="footer-content">
|
<div class="footer-content">
|
||||||
<h3>🧹 КлинСервис</h3>
|
<h3>🧹 КлинСервис</h3>
|
||||||
@@ -156,7 +152,7 @@
|
|||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Простая прокрутка к секции услуг
|
// прокрутка к секции услуг
|
||||||
document.querySelector('.hero-buttons .btn-secondary').onclick = function(e) {
|
document.querySelector('.hero-buttons .btn-secondary').onclick = function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
document.getElementById('services').scrollIntoView({
|
document.getElementById('services').scrollIntoView({
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Illuminate\Foundation\Application;
|
use Illuminate\Contracts\Http\Kernel;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
define('LARAVEL_START', microtime(true));
|
define('LARAVEL_START', microtime(true));
|
||||||
|
|
||||||
// Determine if the application is in maintenance mode...
|
|
||||||
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
|
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
|
||||||
require $maintenance;
|
require $maintenance;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the Composer autoloader...
|
|
||||||
require __DIR__.'/../vendor/autoload.php';
|
require __DIR__.'/../vendor/autoload.php';
|
||||||
|
|
||||||
// Bootstrap Laravel and handle the request...
|
|
||||||
/** @var Application $app */
|
|
||||||
$app = require_once __DIR__.'/../bootstrap/app.php';
|
$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 App\Http\Controllers\CategoriesController;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use App\Http\Controllers\AuthController;
|
||||||
|
|
||||||
Route::get('/user', function (Request $request) {
|
Route::get('/user', function (Request $request) {
|
||||||
return $request->user();
|
return $request->user();
|
||||||
@@ -15,6 +16,8 @@ Route::get('/user', function (Request $request) {
|
|||||||
// РЕГИСТРАЦИЯ ТОЛЬКО КЛИЕНТОВ (публичный)
|
// РЕГИСТРАЦИЯ ТОЛЬКО КЛИЕНТОВ (публичный)
|
||||||
Route::post('/register', [UserController::class, 'register']);
|
Route::post('/register', [UserController::class, 'register']);
|
||||||
|
|
||||||
|
Route::post('/login', [AuthController::class, 'login']);
|
||||||
|
|
||||||
// Существующие роуты categories
|
// Существующие роуты categories
|
||||||
Route::get('/categories', [CategoriesController::class, 'index'])->middleware('auth:sanctum');
|
Route::get('/categories', [CategoriesController::class, 'index'])->middleware('auth:sanctum');
|
||||||
Route::get('/categories/{id}', [CategoriesController::class, 'show']);
|
Route::get('/categories/{id}', [CategoriesController::class, 'show']);
|
||||||
@@ -24,7 +27,7 @@ Route::post('/categories', [CategoriesController::class, 'create']);
|
|||||||
Route::get('/availability', [AvailabilitiesController::class, 'publicAvailability']);
|
Route::get('/availability', [AvailabilitiesController::class, 'publicAvailability']);
|
||||||
|
|
||||||
// КЛИЕНТСКИЕ РОУТЫ БРОНИРОВАНИЙ (auth:sanctum)
|
// КЛИЕНТСКИЕ РОУТЫ БРОНИРОВАНИЙ (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', [BookingsController::class, 'store']);
|
||||||
Route::post('/bookings/{id}/cancel', [BookingsController::class, 'cancel']);
|
Route::post('/bookings/{id}/cancel', [BookingsController::class, 'cancel']);
|
||||||
Route::post('/bookings/{id}/cancel', [BookingsController::class, 'adminCancel']);
|
Route::post('/bookings/{id}/cancel', [BookingsController::class, 'adminCancel']);
|
||||||
|
|||||||
Reference in New Issue
Block a user