mirror of
https://github.com/LOBSTERVOVA/Tennis-Site.git
synced 2026-04-17 17:40:49 +03:00
добавил управление главными фото страницы
This commit is contained in:
@@ -51,7 +51,7 @@ public class SecurityConfig {
|
||||
.addFilterAt(authenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION)
|
||||
//.addFilterAfter(errorHandlingFilter, SecurityWebFiltersOrder.AUTHENTICATION)
|
||||
.authorizeExchange(exchange -> exchange
|
||||
.pathMatchers("/account/login","/error","/error/**", "/account/logout").permitAll()
|
||||
.pathMatchers("/account/login","/error","/error/**", "/account/logout", "/api/admin/site-settings/public").permitAll()
|
||||
.pathMatchers("/account/**", "/admin", "/api/admin/**").authenticated()
|
||||
.anyExchange().permitAll()
|
||||
)
|
||||
|
||||
@@ -8,10 +8,12 @@ import com.example.dateplanner.dto.PageResponse;
|
||||
import com.example.dateplanner.models.entities.AppUser;
|
||||
import com.example.dateplanner.models.entities.Court;
|
||||
import com.example.dateplanner.models.entities.EventApplication;
|
||||
import com.example.dateplanner.models.entities.SiteSettings;
|
||||
import com.example.dateplanner.models.entities.TennisClub;
|
||||
import com.example.dateplanner.models.entities.TennisEvent;
|
||||
import com.example.dateplanner.repositories.CourtRepository;
|
||||
import com.example.dateplanner.repositories.EventApplicationRepository;
|
||||
import com.example.dateplanner.repositories.SiteSettingsRepository;
|
||||
import com.example.dateplanner.repositories.TennisClubRepository;
|
||||
import com.example.dateplanner.repositories.TennisEventRepository;
|
||||
import com.example.dateplanner.services.ApplicationService;
|
||||
@@ -41,6 +43,7 @@ public class ApiAdminController {
|
||||
private final EventApplicationRepository applicationRepository;
|
||||
private final ApplicationService applicationService;
|
||||
private final MinioService minioService;
|
||||
private final SiteSettingsRepository siteSettingsRepository;
|
||||
|
||||
// ── Загрузка изображений ───────────────────────────────────────────────────
|
||||
|
||||
@@ -450,4 +453,49 @@ public class ApiAdminController {
|
||||
dto.setCreatedAt(app.getCreatedAt());
|
||||
return dto;
|
||||
}
|
||||
|
||||
// ── Настройки сайта ───────────────────────────────────────────────────────
|
||||
|
||||
@GetMapping("/site-settings")
|
||||
public Mono<SiteSettings> getSiteSettings(@AuthenticationPrincipal AppUser user) {
|
||||
return siteSettingsRepository.findFirstByOrderByUpdatedAtDesc()
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
SiteSettings settings = new SiteSettings();
|
||||
settings.setHeroImageUrl(
|
||||
"https://avatars.mds.yandex.net/get-altay/1880524/2a0000016ef11d02f18395e01a44f0ddbdb7/XXL_height");
|
||||
settings.setBackgroundImageUrl(
|
||||
"https://avatars.mds.yandex.net/get-altay/1880524/2a0000016ef11d02f18395e01a44f0ddbdb7/XXL_height");
|
||||
return siteSettingsRepository.save(settings);
|
||||
}));
|
||||
}
|
||||
|
||||
@PutMapping("/site-settings")
|
||||
public Mono<SiteSettings> updateSiteSettings(@RequestBody SiteSettings body,
|
||||
@AuthenticationPrincipal AppUser user) {
|
||||
return siteSettingsRepository.findFirstByOrderByUpdatedAtDesc()
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
SiteSettings settings = new SiteSettings();
|
||||
return siteSettingsRepository.save(settings);
|
||||
}))
|
||||
.flatMap(existing -> {
|
||||
existing.setHeroImageUrl(body.getHeroImageUrl());
|
||||
existing.setBackgroundImageUrl(body.getBackgroundImageUrl());
|
||||
existing.setUpdatedAt(LocalDateTime.now());
|
||||
return siteSettingsRepository.save(existing);
|
||||
});
|
||||
}
|
||||
|
||||
// Публичный endpoint для главной страницы
|
||||
@GetMapping("/site-settings/public")
|
||||
public Mono<SiteSettings> getPublicSiteSettings() {
|
||||
return siteSettingsRepository.findFirstByOrderByUpdatedAtDesc()
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
SiteSettings settings = new SiteSettings();
|
||||
settings.setHeroImageUrl(
|
||||
"https://avatars.mds.yandex.net/get-altay/1880524/2a0000016ef11d02f18395e01a44f0ddbdb7/XXL_height");
|
||||
settings.setBackgroundImageUrl(
|
||||
"https://avatars.mds.yandex.net/get-altay/1880524/2a0000016ef11d02f18395e01a44f0ddbdb7/XXL_height");
|
||||
return siteSettingsRepository.save(settings);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.example.dateplanner.controllers.web;
|
||||
|
||||
import com.example.dateplanner.models.entities.AppUser;
|
||||
import com.example.dateplanner.models.entities.SiteSettings;
|
||||
import com.example.dateplanner.repositories.SiteSettingsRepository;
|
||||
import com.example.dateplanner.services.UserService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -22,15 +24,29 @@ import java.util.Map;
|
||||
public class AccountController extends BaseWebController {
|
||||
|
||||
private final UserService userService;
|
||||
private final SiteSettingsRepository siteSettingsRepository;
|
||||
|
||||
private static final String DEFAULT_BG_IMAGE =
|
||||
"https://avatars.mds.yandex.net/get-altay/1880524/2a0000016ef11d02f18395e01a44f0ddbdb7/XXL_height";
|
||||
|
||||
@GetMapping("/login")
|
||||
public Mono<Rendering> loginPage() {
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
model.put("title", "Login");
|
||||
model.put("index", "login");
|
||||
return addAuthToModel(model).map(m ->
|
||||
Rendering.view("template").model(m).build()
|
||||
);
|
||||
|
||||
return siteSettingsRepository.findFirstByOrderByUpdatedAtDesc()
|
||||
.map(settings -> {
|
||||
model.put("backgroundImageUrl",
|
||||
settings.getBackgroundImageUrl() != null ? settings.getBackgroundImageUrl() : DEFAULT_BG_IMAGE);
|
||||
return model;
|
||||
})
|
||||
.switchIfEmpty(Mono.fromCallable(() -> {
|
||||
model.put("backgroundImageUrl", DEFAULT_BG_IMAGE);
|
||||
return model;
|
||||
}))
|
||||
.flatMap(this::addAuthToModel)
|
||||
.map(m -> Rendering.view("template").model(m).build());
|
||||
}
|
||||
|
||||
@GetMapping("/profile")
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.example.dateplanner.controllers.web;
|
||||
|
||||
import com.example.dateplanner.models.entities.SiteSettings;
|
||||
import com.example.dateplanner.repositories.SiteSettingsRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Controller;
|
||||
@@ -17,14 +19,30 @@ import java.util.UUID;
|
||||
@RequiredArgsConstructor
|
||||
public class HomeController extends BaseWebController {
|
||||
|
||||
private final SiteSettingsRepository siteSettingsRepository;
|
||||
|
||||
private static final String DEFAULT_HERO_IMAGE =
|
||||
"https://avatars.mds.yandex.net/get-altay/1880524/2a0000016ef11d02f18395e01a44f0ddbdb7/XXL_height";
|
||||
|
||||
@GetMapping("/")
|
||||
public Mono<Rendering> home() {
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
model.put("title", "Home");
|
||||
model.put("index", "home");
|
||||
return addAuthToModel(model).map(m ->
|
||||
Rendering.view("template").model(m).build()
|
||||
);
|
||||
|
||||
return siteSettingsRepository.findFirstByOrderByUpdatedAtDesc()
|
||||
.map(settings -> {
|
||||
model.put("heroImageUrl", settings.getHeroImageUrl() != null ? settings.getHeroImageUrl() : DEFAULT_HERO_IMAGE);
|
||||
model.put("backgroundImageUrl", settings.getBackgroundImageUrl() != null ? settings.getBackgroundImageUrl() : DEFAULT_HERO_IMAGE);
|
||||
return model;
|
||||
})
|
||||
.switchIfEmpty(Mono.fromCallable(() -> {
|
||||
model.put("heroImageUrl", DEFAULT_HERO_IMAGE);
|
||||
model.put("backgroundImageUrl", DEFAULT_HERO_IMAGE);
|
||||
return model;
|
||||
}))
|
||||
.flatMap(this::addAuthToModel)
|
||||
.map(m -> Rendering.view("template").model(m).build());
|
||||
}
|
||||
|
||||
@GetMapping("/clubs/{id}")
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.example.dateplanner.models.entities;
|
||||
|
||||
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 = "site_settings")
|
||||
public class SiteSettings {
|
||||
@Id
|
||||
@Column("uuid")
|
||||
private UUID uuid;
|
||||
|
||||
@Column("hero_image_url")
|
||||
private String heroImageUrl;
|
||||
|
||||
@Column("background_image_url")
|
||||
private String backgroundImageUrl;
|
||||
|
||||
@Column("updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.example.dateplanner.repositories;
|
||||
|
||||
import com.example.dateplanner.models.entities.SiteSettings;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public interface SiteSettingsRepository extends R2dbcRepository<SiteSettings, UUID> {
|
||||
Mono<SiteSettings> findFirstByOrderByUpdatedAtDesc();
|
||||
}
|
||||
16
src/main/resources/db/migration/V1_0_6__site_settings.sql
Normal file
16
src/main/resources/db/migration/V1_0_6__site_settings.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
-- Site settings for managing hero and background images
|
||||
CREATE TABLE IF NOT EXISTS site_settings (
|
||||
uuid UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
hero_image_url TEXT,
|
||||
background_image_url TEXT,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Insert default settings with the previously hardcoded URLs
|
||||
INSERT INTO site_settings (uuid, hero_image_url, background_image_url, updated_at)
|
||||
VALUES (
|
||||
gen_random_uuid(),
|
||||
'https://avatars.mds.yandex.net/get-altay/1880524/2a0000016ef11d02f18395e01a44f0ddbdb7/XXL_height',
|
||||
'https://avatars.mds.yandex.net/get-altay/1880524/2a0000016ef11d02f18395e01a44f0ddbdb7/XXL_height',
|
||||
CURRENT_TIMESTAMP
|
||||
) ON CONFLICT DO NOTHING;
|
||||
@@ -1,6 +1,7 @@
|
||||
import { initAdminClubs } from './clubsSection.js';
|
||||
import { initAdminEvents } from './eventsSection.js';
|
||||
import { initApplicationsSection } from './applicationsSection.js';
|
||||
import { initSiteImages } from './siteImagesSection.js';
|
||||
|
||||
function onStats(stats) {
|
||||
if (stats.clubs != null) $('#stat-clubs').text(stats.clubs);
|
||||
@@ -12,3 +13,4 @@ function onStats(stats) {
|
||||
initAdminClubs($('#admin-clubs-container'), onStats);
|
||||
initAdminEvents($('#admin-events-container'), onStats);
|
||||
initApplicationsSection($('#admin-applications-container'));
|
||||
initSiteImages();
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
import { getCsrfToken } from '../../utils/helpers.js';
|
||||
|
||||
const API = '/api/admin';
|
||||
|
||||
async function uploadImage(file) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const res = await fetch(`${API}/upload/image`, {
|
||||
method: 'POST',
|
||||
headers: { 'X-XSRF-TOKEN': getCsrfToken() },
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!res.ok) throw new Error('Ошибка загрузки изображения');
|
||||
const data = await res.json();
|
||||
return data.url;
|
||||
}
|
||||
|
||||
function initImageUploader(zoneId) {
|
||||
const zone = document.getElementById(zoneId);
|
||||
if (!zone) return { getUrl: () => null, setUrl: () => {}, clear: () => {} };
|
||||
|
||||
let currentUrl = null;
|
||||
|
||||
zone.addEventListener('click', () => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = 'image/*';
|
||||
input.onchange = async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
zone.classList.add('uploading');
|
||||
zone.innerHTML = '<div class="img-uploader__loading"><div class="spinner-border text-light"></div></div>';
|
||||
try {
|
||||
currentUrl = await uploadImage(file);
|
||||
const cdn = window.cdn || '';
|
||||
zone.innerHTML = `<img src="${cdn}${currentUrl}" class="img-uploader__preview" alt="preview">
|
||||
<button class="img-uploader__remove" type="button"><i class="bi bi-x-lg"></i></button>`;
|
||||
zone.querySelector('.img-uploader__remove').addEventListener('click', (ev) => {
|
||||
ev.stopPropagation();
|
||||
currentUrl = null;
|
||||
zone.innerHTML = `<div class="img-uploader__placeholder">
|
||||
<i class="bi bi-cloud-upload"></i>
|
||||
<span>Нажмите или перетащите изображение</span>
|
||||
</div>`;
|
||||
if (window.onSiteImagesChange) window.onSiteImagesChange();
|
||||
});
|
||||
if (window.onSiteImagesChange) window.onSiteImagesChange();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
zone.innerHTML = `<div class="img-uploader__placeholder">
|
||||
<i class="bi bi-exclamation-triangle text-danger"></i>
|
||||
<span class="text-danger">Ошибка загрузки</span>
|
||||
</div>`;
|
||||
setTimeout(() => {
|
||||
zone.innerHTML = `<div class="img-uploader__placeholder">
|
||||
<i class="bi bi-cloud-upload"></i>
|
||||
<span>Нажмите или перетащите изображение</span>
|
||||
</div>`;
|
||||
}, 2000);
|
||||
} finally {
|
||||
zone.classList.remove('uploading');
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
});
|
||||
|
||||
zone.addEventListener('dragover', (e) => { e.preventDefault(); zone.classList.add('dragover'); });
|
||||
zone.addEventListener('dragleave', () => zone.classList.remove('dragover'));
|
||||
zone.addEventListener('drop', async (e) => {
|
||||
e.preventDefault();
|
||||
zone.classList.remove('dragover');
|
||||
const file = e.dataTransfer.files[0];
|
||||
if (!file || !file.type.startsWith('image/')) return;
|
||||
zone.classList.add('uploading');
|
||||
zone.innerHTML = '<div class="img-uploader__loading"><div class="spinner-border text-light"></div></div>';
|
||||
try {
|
||||
currentUrl = await uploadImage(file);
|
||||
const cdn = window.cdn || '';
|
||||
zone.innerHTML = `<img src="${cdn}${currentUrl}" class="img-uploader__preview" alt="preview">
|
||||
<button class="img-uploader__remove" type="button"><i class="bi bi-x-lg"></i></button>`;
|
||||
zone.querySelector('.img-uploader__remove').addEventListener('click', (ev) => {
|
||||
ev.stopPropagation();
|
||||
currentUrl = null;
|
||||
zone.innerHTML = `<div class="img-uploader__placeholder">
|
||||
<i class="bi bi-cloud-upload"></i>
|
||||
<span>Нажмите или перетащите изображение</span>
|
||||
</div>`;
|
||||
if (window.onSiteImagesChange) window.onSiteImagesChange();
|
||||
});
|
||||
if (window.onSiteImagesChange) window.onSiteImagesChange();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
zone.innerHTML = `<div class="img-uploader__placeholder">
|
||||
<i class="bi bi-exclamation-triangle text-danger"></i>
|
||||
<span class="text-danger">Ошибка загрузки</span>
|
||||
</div>`;
|
||||
setTimeout(() => {
|
||||
zone.innerHTML = `<div class="img-uploader__placeholder">
|
||||
<i class="bi bi-cloud-upload"></i>
|
||||
<span>Нажмите или перетащите изображение</span>
|
||||
</div>`;
|
||||
}, 2000);
|
||||
} finally {
|
||||
zone.classList.remove('uploading');
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
getUrl: () => currentUrl,
|
||||
setUrl: (url) => {
|
||||
currentUrl = url;
|
||||
if (url) {
|
||||
const cdn = window.cdn || '';
|
||||
zone.innerHTML = `<img src="${cdn}${url}" class="img-uploader__preview" alt="preview">
|
||||
<button class="img-uploader__remove" type="button"><i class="bi bi-x-lg"></i></button>`;
|
||||
zone.querySelector('.img-uploader__remove').addEventListener('click', (ev) => {
|
||||
ev.stopPropagation();
|
||||
currentUrl = null;
|
||||
zone.innerHTML = `<div class="img-uploader__placeholder">
|
||||
<i class="bi bi-cloud-upload"></i>
|
||||
<span>Нажмите или перетащите изображение</span>
|
||||
</div>`;
|
||||
if (window.onSiteImagesChange) window.onSiteImagesChange();
|
||||
});
|
||||
}
|
||||
},
|
||||
clear: () => {
|
||||
currentUrl = null;
|
||||
zone.innerHTML = `<div class="img-uploader__placeholder">
|
||||
<i class="bi bi-cloud-upload"></i>
|
||||
<span>Нажмите или перетащите изображение</span>
|
||||
</div>`;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export async function initSiteImages() {
|
||||
const heroUploader = initImageUploader('hero-image-zone');
|
||||
const bgUploader = initImageUploader('bg-image-zone');
|
||||
const saveBtn = document.getElementById('save-site-images-btn');
|
||||
|
||||
if (!saveBtn) return;
|
||||
|
||||
window.onSiteImagesChange = () => {
|
||||
saveBtn.disabled = false;
|
||||
};
|
||||
|
||||
// Загрузка текущих настроек
|
||||
try {
|
||||
const res = await fetch(`${API}/site-settings`, {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-XSRF-TOKEN': getCsrfToken()
|
||||
}
|
||||
});
|
||||
if (res.ok) {
|
||||
const settings = await res.json();
|
||||
if (settings.heroImageUrl) heroUploader.setUrl(settings.heroImageUrl);
|
||||
if (settings.backgroundImageUrl) bgUploader.setUrl(settings.backgroundImageUrl);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Ошибка загрузки настроек изображений:', err);
|
||||
}
|
||||
|
||||
saveBtn.addEventListener('click', async () => {
|
||||
saveBtn.disabled = true;
|
||||
saveBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Сохранение...';
|
||||
|
||||
try {
|
||||
const body = {
|
||||
heroImageUrl: heroUploader.getUrl(),
|
||||
backgroundImageUrl: bgUploader.getUrl()
|
||||
};
|
||||
|
||||
const res = await fetch(`${API}/site-settings`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-XSRF-TOKEN': getCsrfToken()
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
|
||||
if (!res.ok) throw new Error('Ошибка сохранения настроек');
|
||||
|
||||
saveBtn.innerHTML = '<i class="bi bi-check-circle me-1"></i>Сохранено!';
|
||||
setTimeout(() => {
|
||||
saveBtn.innerHTML = '<i class="bi bi-check-circle me-1"></i>Сохранить изображения';
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
saveBtn.disabled = false;
|
||||
saveBtn.innerHTML = '<i class="bi bi-x-circle me-1"></i>Ошибка сохранения';
|
||||
setTimeout(() => {
|
||||
saveBtn.innerHTML = '<i class="bi bi-check-circle me-1"></i>Сохранить изображения';
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -689,6 +689,45 @@
|
||||
<div id="admin-applications-container"></div>
|
||||
</div>
|
||||
|
||||
<!-- ── Site Images section ── -->
|
||||
<div class="section-card" id="site-images-section">
|
||||
<div class="section-title mb-3">
|
||||
<span><i class="bi bi-image me-2"></i>Изображения главной страницы</span>
|
||||
</div>
|
||||
<p class="text-muted mb-4" style="font-size:0.9rem;">Настройте изображения для hero-секции и фона главной страницы</p>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<h6 class="fw-bold mb-3"><i class="bi bi-card-image me-2"></i>Hero изображение</h6>
|
||||
<p class="text-muted mb-2" style="font-size:0.8rem;">Основное изображение справа в hero-секции</p>
|
||||
<div id="hero-image-uploader" class="img-uploader">
|
||||
<div class="img-uploader__zone" id="hero-image-zone">
|
||||
<div class="img-uploader__placeholder">
|
||||
<i class="bi bi-cloud-upload"></i>
|
||||
<span>Нажмите или перетащите изображение</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="fw-bold mb-3"><i class="bi bi-aspect-ratio me-2"></i>Фоновое изображение</h6>
|
||||
<p class="text-muted mb-2" style="font-size:0.8rem;">Фон hero-секции и страницы входа</p>
|
||||
<div id="bg-image-uploader" class="img-uploader">
|
||||
<div class="img-uploader__zone" id="bg-image-zone">
|
||||
<div class="img-uploader__placeholder">
|
||||
<i class="bi bi-cloud-upload"></i>
|
||||
<span>Нажмите или перетащите изображение</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<button id="save-site-images-btn" class="btn-admin" disabled>
|
||||
<i class="bi bi-check-circle me-1"></i>Сохранить изображения
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script type="module" src="/js/site/pages/admin/admin.js"></script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<style>
|
||||
<style th:inline="css">
|
||||
.tennis-bg {
|
||||
background: linear-gradient(rgba(44,62,80,0.9), rgba(44,62,80,0.9)),
|
||||
url('https://avatars.mds.yandex.net/get-altay/1880524/2a0000016ef11d02f18395e01a44f0ddbdb7/XXL_height');
|
||||
url([(${cdn != null ? cdn + backgroundImageUrl : backgroundImageUrl})]);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
color: white;
|
||||
@@ -94,7 +94,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 hero-img mt-4 mt-lg-0">
|
||||
<img src="https://avatars.mds.yandex.net/get-altay/1880524/2a0000016ef11d02f18395e01a44f0ddbdb7/XXL_height"
|
||||
<img th:src="${cdn != null ? cdn + heroImageUrl : heroImageUrl}"
|
||||
class="img-fluid rounded-3 shadow-lg" alt="Теннисный корт">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
<style>
|
||||
:root {
|
||||
--tennis-green: #2ecc71;
|
||||
--tennis-dark: #2c3e50;
|
||||
}
|
||||
|
||||
<style th:inline="css">
|
||||
.login-bg {
|
||||
background: linear-gradient(rgba(44,62,80,0.85), rgba(44,62,80,0.85)),
|
||||
url('https://avatars.mds.yandex.net/get-altay/1880524/2a0000016ef11d02f18395e01a44f0ddbdb7/XXL_height');
|
||||
url([(${cdn != null ? cdn + backgroundImageUrl : backgroundImageUrl})]);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user