diff --git a/src/main/java/com/example/dateplanner/controllers/advice/SecurityAdviceController.java b/src/main/java/com/example/dateplanner/controllers/advice/SecurityAdviceController.java index 8da0ef7..027064f 100644 --- a/src/main/java/com/example/dateplanner/controllers/advice/SecurityAdviceController.java +++ b/src/main/java/com/example/dateplanner/controllers/advice/SecurityAdviceController.java @@ -4,10 +4,8 @@ import com.example.dateplanner.models.entities.AppUser; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.PropertySource; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.web.csrf.CsrfToken; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @ControllerAdvice diff --git a/src/main/java/com/example/dateplanner/controllers/web/AccountController.java b/src/main/java/com/example/dateplanner/controllers/web/AccountController.java index 1891cc0..47783d3 100644 --- a/src/main/java/com/example/dateplanner/controllers/web/AccountController.java +++ b/src/main/java/com/example/dateplanner/controllers/web/AccountController.java @@ -1,17 +1,17 @@ package com.example.dateplanner.controllers.web; -import ch.qos.logback.core.model.Model; import com.example.dateplanner.models.entities.AppUser; +import com.example.dateplanner.services.UserService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.*; import org.springframework.web.reactive.result.view.Rendering; import reactor.core.publisher.Mono; + +import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; @@ -21,15 +21,27 @@ import java.util.Map; @RequestMapping("/account") public class AccountController { + private final UserService userService; + @GetMapping("/profile") public Mono profile() { Map model = new HashMap<>(); model.put("title", "Profile"); model.put("index", "profile"); - return Mono.just( - Rendering.view("template") - .model(model) - .build() - ); + return Mono.just(Rendering.view("template").model(model).build()); } + + @PutMapping("/profile") + @ResponseBody + public Mono> updateProfile( + @AuthenticationPrincipal AppUser currentUser, + @RequestBody UpdateProfileRequest request) { + if (currentUser == null) return Mono.just(ResponseEntity.status(401).build()); + currentUser.setFirstName(request.firstName()); + currentUser.setLastName(request.lastName()); + currentUser.setUpdatedAt(LocalDateTime.now()); + return userService.save(currentUser).map(ResponseEntity::ok); + } + + public record UpdateProfileRequest(String firstName, String lastName) {} } diff --git a/src/main/java/com/example/dateplanner/models/entities/Organization.java b/src/main/java/com/example/dateplanner/models/entities/Organization.java index 0114c9f..7276f32 100644 --- a/src/main/java/com/example/dateplanner/models/entities/Organization.java +++ b/src/main/java/com/example/dateplanner/models/entities/Organization.java @@ -1,16 +1,16 @@ package com.example.dateplanner.models.entities; -import jakarta.persistence.Table; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.data.annotation.Id; import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Table; import java.time.LocalDateTime; import java.util.UUID; @Data @NoArgsConstructor -@Table(name = "organizations") +@Table("organizations") public class Organization { @Id private UUID uuid; diff --git a/src/main/java/com/example/dateplanner/repositories/OrganizationRepository.java b/src/main/java/com/example/dateplanner/repositories/OrganizationRepository.java index 0ef4351..6e399b5 100644 --- a/src/main/java/com/example/dateplanner/repositories/OrganizationRepository.java +++ b/src/main/java/com/example/dateplanner/repositories/OrganizationRepository.java @@ -2,9 +2,10 @@ package com.example.dateplanner.repositories; import com.example.dateplanner.models.entities.Organization; import org.springframework.data.r2dbc.repository.R2dbcRepository; +import reactor.core.publisher.Flux; import java.util.UUID; public interface OrganizationRepository extends R2dbcRepository { - + Flux findAllByOwnerUuid(UUID ownerUuid); } diff --git a/src/main/java/com/example/dateplanner/services/OrganizationService.java b/src/main/java/com/example/dateplanner/services/OrganizationService.java index 91278ad..23938bf 100644 --- a/src/main/java/com/example/dateplanner/services/OrganizationService.java +++ b/src/main/java/com/example/dateplanner/services/OrganizationService.java @@ -1,12 +1,27 @@ package com.example.dateplanner.services; +import com.example.dateplanner.models.entities.Organization; import com.example.dateplanner.repositories.OrganizationRepository; import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.time.LocalDateTime; +import java.util.UUID; @Service @AllArgsConstructor public class OrganizationService { private final OrganizationRepository organizationRepository; + public Flux getByOwner(UUID ownerUuid) { + return organizationRepository.findAllByOwnerUuid(ownerUuid); + } + + public Mono create(Organization organization) { + organization.setCreatedAt(LocalDateTime.now()); + organization.setUpdatedAt(LocalDateTime.now()); + return organizationRepository.save(organization); + } } diff --git a/src/main/java/com/example/dateplanner/services/UserService.java b/src/main/java/com/example/dateplanner/services/UserService.java index a21129c..1b19165 100644 --- a/src/main/java/com/example/dateplanner/services/UserService.java +++ b/src/main/java/com/example/dateplanner/services/UserService.java @@ -1,5 +1,6 @@ package com.example.dateplanner.services; +import com.example.dateplanner.models.entities.AppUser; import com.example.dateplanner.repositories.AppUserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -20,4 +21,8 @@ public class UserService implements ReactiveUserDetailsService { log.info("username {}", username); return userRepository.findByPhone(username).flatMap(Mono::just).cast(UserDetails.class); } + + public Mono save(AppUser user) { + return userRepository.save(user); + } } diff --git a/src/main/resources/static/css/profile.css b/src/main/resources/static/css/profile.css index 0c0ce6e..0bd8e64 100644 --- a/src/main/resources/static/css/profile.css +++ b/src/main/resources/static/css/profile.css @@ -1,10 +1,14 @@ +/* ═══════════════════════════════════════════════════════════════ + LAYOUT — сайдбар и основной контент + ═══════════════════════════════════════════════════════════════ */ + .sidebar { background: white; border-radius: 20px; padding: 25px; - box-shadow: 0 10px 30px rgba(0,0,0,0.08); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08); position: sticky; - top: 30px; + top: 80px; /* ниже fixed-header */ height: fit-content; } @@ -12,22 +16,45 @@ background: white; border-radius: 20px; padding: 40px; - box-shadow: 0 10px 30px rgba(0,0,0,0.08); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08); min-height: 600px; } +/* ═══════════════════════════════════════════════════════════════ + SIDEBAR — аватар, имя, прогресс + ═══════════════════════════════════════════════════════════════ */ + +/* Аватар-инициалы в сайдбаре */ +.profile-sidebar-avatar { + width: 90px; + height: 90px; + border-radius: 50%; + background: linear-gradient(135deg, #e74c3c 0%, #fd79a8 100%); + color: white; + display: flex; + align-items: center; + justify-content: center; + font-size: 28px; + font-weight: 700; + box-shadow: 0 8px 20px rgba(231, 76, 60, 0.3); + border: 4px solid white; +} + +/* Устаревший аватар на основе фото (оставлен для совместимости) */ .profile-user-avatar { width: 120px; height: 120px; border-radius: 50%; object-fit: cover; border: 5px solid white; - box-shadow: 0 10px 30px rgba(0,0,0,0.1); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); margin-bottom: 20px; } +/* Бейдж «Профиль на X%» */ .match-badge { - background: linear-gradient(45deg, #E74C3CFF, #FD79A8FF); + display: inline-block; + background: linear-gradient(45deg, #e74c3c, #fd79a8); color: white; padding: 5px 15px; border-radius: 20px; @@ -35,8 +62,9 @@ font-weight: 600; } +/* Блок заполненности профиля */ .profile-completion { - background: #F9F7F7FF; + background: #f9f7f7; border-radius: 15px; padding: 20px; margin: 20px 0; @@ -45,37 +73,280 @@ .completion-percentage { font-size: 2.5rem; font-weight: 700; - color: #E74C3CFF; + color: #e74c3c; line-height: 1; } +/* Прогресс-бар */ .progress { height: 10px !important; border-radius: 5px !important; } .progress-bar { - background-color: #E74C3CFF !important; + background-color: #e74c3c !important; border-radius: 5px; } +/* ═══════════════════════════════════════════════════════════════ + SIDEBAR — навигация по разделам + ═══════════════════════════════════════════════════════════════ */ + .profile-nav-link-custom { - color: #2D3436FF; - padding: 15px 20px; - border-radius: 15px; - margin-bottom: 10px; - transition: all 0.3s; + display: flex; + align-items: center; + color: #2d3436; + padding: 13px 18px; + border-radius: 12px; + margin-bottom: 6px; + transition: all 0.25s; font-weight: 500; + text-decoration: none; + position: relative; } .profile-nav-link-custom:hover { - background: rgba(231, 76, 60, 0.1) !important; - color: #E74C3CFF !important; - transform: translateX(5px) !important; + background: rgba(231, 76, 60, 0.08); + color: #e74c3c; + transform: translateX(4px); } .profile-nav-link-custom.active { - background: rgba(231, 76, 60, 0.1) !important; - color: #E74C3CFF !important; - border-left: 4px solid #E74C3CFF !important; -} \ No newline at end of file + background: rgba(231, 76, 60, 0.1); + color: #e74c3c; + border-left: 4px solid #e74c3c; + padding-left: 14px; +} + +/* Счётчик уведомлений на пункте навигации */ +.notification-badge { + margin-left: auto; + background: #e74c3c; + color: white; + font-size: 0.72rem; + font-weight: 700; + padding: 2px 7px; + border-radius: 20px; + line-height: 1.4; +} + +/* ═══════════════════════════════════════════════════════════════ + SECTION TITLE — заголовок внутри секции + ═══════════════════════════════════════════════════════════════ */ + +.section-title { + border-bottom: 3px solid #f9f7f7; + padding-bottom: 15px; + margin-bottom: 30px; + position: relative; + font-weight: 700; +} + +/* Красная черта под заголовком */ +.section-title::after { + content: ''; + position: absolute; + bottom: -3px; + left: 0; + width: 80px; + height: 3px; + background: #e74c3c; + border-radius: 3px; +} + +/* ═══════════════════════════════════════════════════════════════ + МОЙ ПРОФИЛЬ — карточка и поля + ═══════════════════════════════════════════════════════════════ */ + +/* Контейнер блока профиля */ +.profile-info-card { + background: #f9f7f7; + border-radius: 16px; + padding: 28px; +} + +/* Большой аватар-инициалы в блоке профиля */ +.profile-big-avatar { + width: 80px; + height: 80px; + border-radius: 50%; + background: linear-gradient(135deg, #e74c3c 0%, #fd79a8 100%); + color: white; + display: flex; + align-items: center; + justify-content: center; + font-size: 26px; + font-weight: 700; + flex-shrink: 0; + box-shadow: 0 5px 20px rgba(231, 76, 60, 0.3); +} + +/* Бейдж роли в блоке профиля */ +.profile-role-badge { + display: inline-block; + background: rgba(231, 76, 60, 0.1); + color: #e74c3c; + padding: 3px 14px; + border-radius: 20px; + font-size: 0.8rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* Сетка информационных полей */ +.profile-info-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + gap: 20px; +} + +/* Один элемент поля */ +.profile-info-item { + display: flex; + align-items: flex-start; + gap: 12px; +} + +/* Иконка поля */ +.profile-info-icon { + color: #e74c3c; + font-size: 17px; + margin-top: 2px; + flex-shrink: 0; +} + +/* Подпись поля */ +.profile-info-label { + font-size: 0.75rem; + color: #aaa; + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 2px; +} + +/* Значение поля */ +.profile-info-value { + font-weight: 600; + color: #2d3436; + font-size: 0.95rem; +} + +/* ═══════════════════════════════════════════════════════════════ + МОИ ОРГАНИЗАЦИИ — карточки и пустое состояние + ═══════════════════════════════════════════════════════════════ */ + +/* Карточка организации */ +.org-card { + background: #f9f7f7; + border-radius: 16px; + padding: 20px; + height: 100%; + transition: box-shadow 0.2s, transform 0.2s; +} + +.org-card:hover { + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); + transform: translateY(-2px); +} + +/* Шапка карточки: лого + мета */ +.org-card-header { + display: flex; + align-items: center; + gap: 14px; + margin-bottom: 12px; +} + +/* Обёртка логотипа */ +.org-card-logo-wrap { + width: 56px; + height: 56px; + border-radius: 14px; + overflow: hidden; + flex-shrink: 0; + background: white; + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1); + display: flex; + align-items: center; + justify-content: center; +} + +/* Логотип-изображение */ +.org-card-logo-img { + width: 100%; + height: 100%; + object-fit: cover; +} + +/* Логотип-инициалы (если нет картинки) */ +.org-card-logo-initials { + background: linear-gradient(135deg, #e74c3c 0%, #fd79a8 100%); + color: white; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + font-weight: 700; +} + +/* Название организации */ +.org-card-title { + font-size: 1rem; + font-weight: 700; + color: #2d3436; + margin-bottom: 2px; +} + +/* Дата создания */ +.org-card-date { + font-size: 0.8rem; + color: #aaa; +} + +/* Описание */ +.org-card-desc { + font-size: 0.88rem; + color: #636e72; + line-height: 1.5; + margin-bottom: 0; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} + +/* Пустое состояние (нет организаций) */ +.org-empty-state { + text-align: center; + padding: 60px 30px; + background: #f9f7f7; + border-radius: 20px; +} + +/* ═══════════════════════════════════════════════════════════════ + МОДАЛКА СОЗДАНИЯ ОРГАНИЗАЦИИ — загрузчик логотипа + ═══════════════════════════════════════════════════════════════ */ + +/* Область загрузки логотипа */ +.org-logo-upload-area { + width: 100%; + aspect-ratio: 1; + border: 2px dashed #ddd; + border-radius: 14px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background: #f9f7f7; + overflow: hidden; + cursor: pointer; + transition: border-color 0.2s, background 0.2s; +} + +.org-logo-upload-area:hover { + border-color: #e74c3c; + background: rgba(231, 76, 60, 0.03); +} diff --git a/src/main/resources/static/js/site/blocks/header.js b/src/main/resources/static/js/site/blocks/header.js index 513f0d3..3dad9b1 100644 --- a/src/main/resources/static/js/site/blocks/header.js +++ b/src/main/resources/static/js/site/blocks/header.js @@ -1,6 +1,5 @@ function initHeader($header){ console.log("init header date") - let authSection if(!auth) { authSection = ` diff --git a/src/main/resources/static/js/site/pages/account/init-modal-map.js b/src/main/resources/static/js/site/pages/account/init-modal-map.js new file mode 100644 index 0000000..d1a086a --- /dev/null +++ b/src/main/resources/static/js/site/pages/account/init-modal-map.js @@ -0,0 +1,37 @@ +// function initModalMap() { +// let map = new ymaps3.map('map-container', { +// center: [55.756806, 37.622727], +// zoom: 11 +// }) +// } +// await ymaps3.ready(initModalMap) +// + +initMap(); + +async function initMap() { + // Промис `ymaps3.ready` будет зарезолвлен, когда загрузятся все компоненты основного модуля API + await ymaps3.ready; + + const {YMap, YMapDefaultSchemeLayer} = ymaps3; + + // Иницилиазируем карту + const map = new YMap( + // Передаём ссылку на HTMLElement контейнера + document.getElementById('map-container'), + + // Передаём параметры инициализации карты + { + location: { + // Координаты центра карты + center: [37.588144, 55.733842], + + // Уровень масштабирования + zoom: 10 + } + } + ); + + // Добавляем слой для отображения схематической карты + map.addChild(new YMapDefaultSchemeLayer()); +} diff --git a/src/main/resources/static/js/site/pages/account/my-organizations-block.js b/src/main/resources/static/js/site/pages/account/my-organizations-block.js new file mode 100644 index 0000000..241dcbd --- /dev/null +++ b/src/main/resources/static/js/site/pages/account/my-organizations-block.js @@ -0,0 +1,607 @@ +function renderMyOrganizations($organizationsBlockContainer) { + + // ── Модалка создания организации ────────────────────────────────────────── + if (!$('#createOrganizationModal').length) { + $('body').append(` + + `); + } + + // ── Модалка создания места для свидания ─────────────────────────────────── + if (!$('#createDatingPlaceModal').length) { + $('body').append(` + + `); + } + + // ── Секция "Мои организации" ────────────────────────────────────────────── + $organizationsBlockContainer.append(` +
+

Мои организации

+ +
+

+ Управляйте своими организациями — добавляйте места и мероприятия для свиданий +

+
+
+
+

Загружаем организации...

+
+
+ `); + + // ── Загрузка организаций ────────────────────────────────────────────────── + function loadOrganizations() { + fetch('/api/organizations/my') + .then(r => r.json()) + .then(orgs => { + const $list = $('#orgCardsList'); + $list.empty(); + + if (!orgs.length) { + $list.append(` +
+
+ +
У вас пока нет организаций
+

+ Создайте первую организацию и начните добавлять места для свиданий +

+ +
+
+ `); + return; + } + + orgs.forEach(org => { + const initials = org.title + ? org.title.substring(0, 2).toUpperCase() + : 'ОГ'; + const logo = org.logo + ? `${org.title}` + : `
${initials}
`; + const desc = org.description + ? org.description.replace(/<[^>]*>/g, '').substring(0, 100) + '...' + : 'Описание не добавлено'; + const date = org.createdAt + ? new Date(org.createdAt).toLocaleDateString('ru-RU') + : ''; + + $list.append(` +
+
+
+
${logo}
+
+
${org.title}
+ ${date ? `${date}` : ''} +
+
+

${desc}

+
+ + +
+
+
+ `); + }); + }) + .catch(err => { + console.error('Ошибка загрузки организаций:', err); + $('#orgCardsList').html(` +
+ +

Не удалось загрузить организации

+
+ `); + }); + } + + // ── Обработчики модалки создания организации ────────────────────────────── + + // Превью логотипа + $(document).on('change', '#orgLogoFile', function () { + const file = this.files[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = e => { + $('#orgLogoPreviewArea').html(` + Logo + `); + }; + reader.readAsDataURL(file); + }); + + $(document).on('click', '#orgLogoPickBtn', () => $('#orgLogoFile').trigger('click')); + $(document).on('click', '#orgLogoPreviewArea', () => $('#orgLogoFile').trigger('click')); + + // Создание организации + $(document).on('click', '#saveOrganizationBtn', async function () { + const title = $('#orgTitle').val().trim(); + if (!title) { + $('#orgTitle').addClass('is-invalid').focus(); + return; + } + $('#orgTitle').removeClass('is-invalid'); + + const $btn = $(this).prop('disabled', true) + .html('Создаём...'); + + try { + const response = await fetch('/api/organizations', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-XSRF-TOKEN': csrf?.token ?? '' + }, + body: JSON.stringify({ + title, + description: $('#orgDescription').val().trim() || null + }) + }); + + if (response.ok) { + bootstrap.Modal.getInstance(document.getElementById('createOrganizationModal')).hide(); + $('#createOrganizationForm')[0].reset(); + $('#orgLogoPreviewArea').html(` + +

Нажмите для выбора

+ `); + loadOrganizations(); + showToast('info', `Организация «${title}» успешно создана`); + } else { + showToast('error', 'Не удалось создать организацию'); + } + } catch (err) { + console.error('Org create error:', err); + showToast('error', 'Ошибка соединения с сервером'); + } finally { + $btn.prop('disabled', false).html('Создать организацию'); + } + }); + + // ── Обработчики модалки создания места ─────────────────────────────────── + + // Переключение расписания + $(document).on('change', 'input[name="scheduleType"]', function () { + if (this.value === 'onetime') { + $('#oneTimeSchedule').removeClass('d-none'); + $('#regularSchedule').addClass('d-none'); + } else { + $('#oneTimeSchedule').addClass('d-none'); + $('#regularSchedule').removeClass('d-none'); + } + }); + + // Добавление дня расписания + let scheduleItemIndex = 0; + $(document).on('click', '#addScheduleItem', function () { + const template = document.getElementById('scheduleItemTemplate'); + const clone = template.content.cloneNode(true); + const item = clone.querySelector('.schedule-item'); + item.dataset.index = scheduleItemIndex++; + item.querySelector('.remove-schedule').addEventListener('click', function () { + this.closest('.schedule-item').remove(); + }); + document.getElementById('scheduleItems').appendChild(clone); + }); + + // Превью фото места + $(document).on('change', '#placePhoto', function () { + const file = this.files[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = e => { + $('#photoPreview').find('img').attr('src', e.target.result); + $('#photoPreview').removeClass('d-none'); + }; + reader.readAsDataURL(file); + }); + + // Авто-категория по цене + $(document).on('input', '#exactPrice', function () { + const price = parseInt(this.value); + if (isNaN(price) || price <= 0) { + $('#priceInfo').addClass('d-none'); + return; + } + let cat, val; + if (price < 2000) { cat = 'Эконом'; val = 'ECONOMY'; } + else if (price <= 5000){ cat = 'Средний'; val = 'AVERAGE'; } + else { cat = 'Премиум'; val = 'PREMIUM'; } + $('#priceType').val(val); + $('#selectedPriceCategory').text(`${cat} (${price}₽)`); + $('#priceInfo').removeClass('d-none'); + }); + + // Бесплатное событие + $(document).on('change', '#freeEvent', function () { + if (this.checked) { + $('#exactPrice').val(0).prop('disabled', true); + $('#priceType').val('ECONOMY'); + $('#selectedPriceCategory').text('Бесплатно'); + $('#priceInfo').removeClass('d-none'); + } else { + $('#exactPrice').val('').prop('disabled', false); + $('#priceType').val(''); + $('#priceInfo').addClass('d-none'); + } + }); + + // Отправка формы создания места + $(document).on('submit', '#createDatingPlaceForm', async function (e) { + e.preventDefault(); + + let price = 0, priceType = null; + if ($('#freeEvent').is(':checked')) { + priceType = 'ECONOMY'; + } else { + const exactPrice = $('#exactPrice').val(); + if (exactPrice) { + price = parseInt(exactPrice); + if (price < 2000) priceType = 'ECONOMY'; + else if (price <= 5000) priceType = 'AVERAGE'; + else priceType = 'PREMIUM'; + } else { + priceType = $('#priceType').val(); + } + } + + const scheduleType = $('input[name="scheduleType"]:checked').val(); + const formData = { + title: $('#placeTitle').val(), + description: $('#placeDescription').val(), + location: $('#placeLocation').val(), + coordinates: $('#placeCoordinates').val() || null, + datingType: $('#datingType').val(), + price, priceType, + startTime: null, + duration: null, + scheduleUuids: [], + expirationDate: null + }; + + if (scheduleType === 'onetime') { + const st = $('#eventStartTime').val(); + if (st) formData.startTime = new Date(st).toISOString(); + formData.duration = parseInt($('#eventDuration').val()) || null; + const exp = $('#expirationDate').val(); + if (exp) formData.expirationDate = new Date(exp).toISOString(); + } + + try { + const response = await fetch('/api/dating-places', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-XSRF-TOKEN': csrf?.token ?? '' + }, + body: JSON.stringify(formData) + }); + + if (response.ok) { + bootstrap.Modal.getInstance(document.getElementById('createDatingPlaceModal')).hide(); + showToast('info', 'Место успешно создано!'); + } else { + showToast('error', 'Ошибка при создании места'); + } + } catch (err) { + console.error('Dating place create error:', err); + showToast('error', 'Ошибка соединения с сервером'); + } + }); + + // Инициализация + loadOrganizations(); +} + +renderMyOrganizations($('#organizationsSection')); diff --git a/src/main/resources/templates/blocks/deepseek_html_20260202_b82073.html b/src/main/resources/templates/blocks/deepseek_html_20260202_b82073.html index 53b0a8d..bb59fee 100644 --- a/src/main/resources/templates/blocks/deepseek_html_20260202_b82073.html +++ b/src/main/resources/templates/blocks/deepseek_html_20260202_b82073.html @@ -1,652 +1,652 @@ - - - - - - DatePlanner - Идеальные места для свиданий - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - -
- -
❤️
-
❤️
-
❤️
-
❤️
- -
-
-
-

Найдите идеальное место для свидания

-

Более 500 проверенных локаций: от романтических ужинов до экстремальных приключений. Подберите свидание по настроению, бюджету и интересам.

- -
-
- Свидание в парке -
-
-
-
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - -
- -
+ + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/pages/profile.html b/src/main/resources/templates/pages/profile.html index 17afbb2..7527f75 100644 --- a/src/main/resources/templates/pages/profile.html +++ b/src/main/resources/templates/pages/profile.html @@ -1,85 +1,86 @@
-
+ +
+ +
-
-1 -
+
-2 -
-
-3 +

Мои предложения

+

Раздел в разработке

+
-4 +

Мероприятия

+

Раздел в разработке

-5 +

Настройки

+

Раздел в разработке

+ \ No newline at end of file + $(`#${sectionId}`).removeClass('d-none').addClass('d-block'); + }); + + // Заполняем сайдбар данными из переменной user (инжектированной сервером) + if (user) { + const initials = `${user.firstName.charAt(0)}${user.lastName.charAt(0)}`; + $('#sidebarAvatarInitials').text(initials); + $('#sidebarFullName').text(`${user.firstName} ${user.lastName}`); + $('#sidebarPhone').text(user.phone); + $('#sidebarRoleBadge').text(user.role || 'USER'); + } + + + + + diff --git a/src/main/resources/templates/template.html b/src/main/resources/templates/template.html index 46a3b23..2fa8708 100644 --- a/src/main/resources/templates/template.html +++ b/src/main/resources/templates/template.html @@ -6,6 +6,7 @@ +