new file: begushiybashkir/bbvue/public/images/locations/1mayPark.webp
new file: begushiybashkir/bbvue/public/images/locations/dinamo.jpg new file: begushiybashkir/bbvue/public/images/locations/riverSide.jpeg modified: begushiybashkir/bbvue/src/views/Training.vue modified: serv_nginx/api_bb/internal/handlers/handler_util.go modified: serv_nginx/api_bb/internal/handlers/user.go modified: serv_nginx/api_bb/internal/routes/routes.go modified: serv_nginx/api_bb/internal/service/auth_service.go modified: serv_nginx/api_bb/internal/service/user_service.go add photo location into trainings page
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
@@ -28,19 +28,14 @@
|
||||
<div class="container">
|
||||
<h2 class="section-title">📅 Расписание тренировок</h2>
|
||||
<p class="section-subtitle">Регулярные занятия в лучших локациях Уфы</p>
|
||||
|
||||
|
||||
<div class="schedule-container">
|
||||
<div class="schedule-grid">
|
||||
<div
|
||||
v-for="day in schedule"
|
||||
:key="day.name"
|
||||
class="schedule-day"
|
||||
:class="{
|
||||
'training-day': day.time !== '--:--',
|
||||
'rest-day': day.time === '--:--',
|
||||
'today': isToday(day.name)
|
||||
}"
|
||||
>
|
||||
<div v-for="day in schedule" :key="day.name" class="schedule-day" :class="{
|
||||
'training-day': day.time !== '--:--',
|
||||
'rest-day': day.time === '--:--',
|
||||
'today': isToday(day.name)
|
||||
}">
|
||||
<div class="day-header">
|
||||
<h4>{{ day.name }}</h4>
|
||||
<span class="day-badge" v-if="isToday(day.name)">Сегодня</span>
|
||||
@@ -55,19 +50,11 @@
|
||||
📍 {{ day.location }}
|
||||
</div>
|
||||
<div class="day-features" v-if="day.features">
|
||||
<span
|
||||
v-for="feature in day.features"
|
||||
:key="feature"
|
||||
class="feature-tag"
|
||||
>
|
||||
<span v-for="feature in day.features" :key="feature" class="feature-tag">
|
||||
{{ feature }}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
v-if="day.time !== '--:--'"
|
||||
class="btn-day"
|
||||
@click="openTrainingModal(day)"
|
||||
>
|
||||
<button v-if="day.time !== '--:--'" class="btn-day" @click="openTrainingModal(day)">
|
||||
🏃 Записаться
|
||||
</button>
|
||||
</div>
|
||||
@@ -150,7 +137,9 @@
|
||||
<div class="locations-grid">
|
||||
<div class="location-card">
|
||||
<div class="location-image">
|
||||
<div class="image-placeholder">🏞️</div>
|
||||
<div class="image-placeholder">
|
||||
<img :src="getImageUrl('locations/riverSide.jpeg')" alt="Набережная Уфа Монумент">
|
||||
</div>
|
||||
</div>
|
||||
<div class="location-info">
|
||||
<h3>Набережная</h3>
|
||||
@@ -165,7 +154,9 @@
|
||||
</div>
|
||||
<div class="location-card">
|
||||
<div class="location-image">
|
||||
<div class="image-placeholder">🏟️</div>
|
||||
<div class="image-placeholder">
|
||||
<img :src="getImageUrl('locations/dinamo.jpg')" alt="Стадион Динамо">
|
||||
</div>
|
||||
</div>
|
||||
<div class="location-info">
|
||||
<h3>Стадион "Динамо"</h3>
|
||||
@@ -178,6 +169,23 @@
|
||||
<p class="location-desc">Лучшее место для работы над скоростью и техникой</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="location-card">
|
||||
<div class="location-image">
|
||||
<div class="image-placeholder">
|
||||
<img :src="getImageUrl('locations/1mayPark.webp')" alt="Парк Первомайский ост. ДК УМПО">
|
||||
</div>
|
||||
</div>
|
||||
<div class="location-info">
|
||||
<h3>Парк первомайский</h3>
|
||||
<p class="location-address">ДК УМПО Калининский р-н</p>
|
||||
<div class="location-features">
|
||||
<span class="feature">📏 Круг 2.5 км</span>
|
||||
<span class="feature">💡 Освещение</span>
|
||||
<span class="feature">🌳 Парк</span>
|
||||
</div>
|
||||
<p class="location-desc">Идеальное место для групповых тренировок и спецбеговых упраждений</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -240,23 +248,11 @@
|
||||
<form class="signup-form" @submit.prevent="handleSignup">
|
||||
<div class="form-group">
|
||||
<label for="name">Ваше имя *</label>
|
||||
<input
|
||||
id="name"
|
||||
v-model="signupForm.name"
|
||||
type="text"
|
||||
required
|
||||
placeholder="Введите ваше имя"
|
||||
>
|
||||
<input id="name" v-model="signupForm.name" type="text" required placeholder="Введите ваше имя">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="phone">Телефон *</label>
|
||||
<input
|
||||
id="phone"
|
||||
v-model="signupForm.phone"
|
||||
type="tel"
|
||||
required
|
||||
placeholder="+7 (999) 123-45-67"
|
||||
>
|
||||
<input id="phone" v-model="signupForm.phone" type="tel" required placeholder="+7 (999) 123-45-67">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="level">Уровень подготовки</label>
|
||||
@@ -290,51 +286,53 @@ export default {
|
||||
level: ''
|
||||
},
|
||||
schedule: [
|
||||
{
|
||||
name: 'Понедельник',
|
||||
time: '19:30',
|
||||
{
|
||||
name: 'Понедельник',
|
||||
time: '19:30',
|
||||
activity: 'Техника бега + ОФП',
|
||||
location: 'Парк Якутова',
|
||||
features: ['Коррекция техники', 'Силовые упражнения']
|
||||
},
|
||||
{
|
||||
name: 'Вторник',
|
||||
time: '19:00',
|
||||
{
|
||||
name: 'Вторник',
|
||||
time: '19:00',
|
||||
activity: 'Тренировка',
|
||||
location: 'Парк первомайский',
|
||||
features: ['Легкий бег', 'Спецупражнения']
|
||||
},
|
||||
{
|
||||
name: 'Среда',
|
||||
time: '19:30',
|
||||
{
|
||||
name: 'Среда',
|
||||
time: '19:30',
|
||||
activity: 'Техника бега + СБУ',
|
||||
location: 'Стадион Динамо',
|
||||
features: ['Спецупражнения', 'Работа над скоростью']
|
||||
},
|
||||
{
|
||||
name: 'Четверг',
|
||||
time: '19:00',
|
||||
{
|
||||
name: 'Четверг',
|
||||
time: '19:00',
|
||||
activity: 'Тренировка',
|
||||
location: 'Парк первомайский',
|
||||
features: ['Самостоятельно', 'Растяжка']
|
||||
},
|
||||
{
|
||||
name: 'Пятница',
|
||||
time: '--:--',
|
||||
{
|
||||
name: 'Пятница',
|
||||
time: '--:--',
|
||||
activity: 'Восстановление',
|
||||
location: 'Свободное',
|
||||
features: ['Самостоятельно', 'Йога/Плавание']
|
||||
},
|
||||
{
|
||||
name: 'Суббота',
|
||||
time: '10:00',
|
||||
{
|
||||
name: 'Суббота',
|
||||
time: '10:00',
|
||||
activity: 'Длительный кросс',
|
||||
location: 'Лесопарковая зона',
|
||||
features: ['Аэробная выносливость', '15-20 км']
|
||||
},
|
||||
{
|
||||
name: 'Воскресенье',
|
||||
time: '--:--',
|
||||
{
|
||||
name: 'Воскресенье',
|
||||
time: '--:--',
|
||||
activity: 'Восстановление',
|
||||
location: 'Свободное',
|
||||
features: ['Полный отдых', 'Восстановительные процедуры']
|
||||
}
|
||||
]
|
||||
@@ -347,6 +345,13 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getImageUrl(path) {
|
||||
// В продакшене замените на правильный путь
|
||||
const baseUrl = import.meta.env.BASE_URL
|
||||
// Путь от корня public/
|
||||
console.log(`${baseUrl}images/${path}`)
|
||||
return `${baseUrl}images/${path}`
|
||||
},
|
||||
isToday(dayName) {
|
||||
return dayName === this.today
|
||||
},
|
||||
@@ -369,7 +374,7 @@ export default {
|
||||
training: this.selectedTraining,
|
||||
user: this.signupForm
|
||||
})
|
||||
|
||||
|
||||
// Имитация успешной записи
|
||||
alert(`Спасибо, ${this.signupForm.name}! Вы записаны на тренировку. Тренер свяжется с вами для подтверждения.`)
|
||||
this.closeModal()
|
||||
@@ -700,6 +705,9 @@ export default {
|
||||
overflow: hidden;
|
||||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.08);
|
||||
transition: transform 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.location-card:hover {
|
||||
@@ -708,24 +716,42 @@ export default {
|
||||
|
||||
.location-image {
|
||||
height: 200px;
|
||||
background: linear-gradient(135deg, #2e8b57 0%, #26734a 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image-placeholder {
|
||||
font-size: 4rem;
|
||||
color: white;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.image-placeholder img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.location-card:hover .image-placeholder img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.location-info {
|
||||
padding: 2rem;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.location-info h3 {
|
||||
color: #2e8b57;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.location-address {
|
||||
@@ -747,11 +773,13 @@ export default {
|
||||
padding: 0.3rem 0.8rem;
|
||||
border-radius: 15px;
|
||||
font-size: 0.8rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.location-desc {
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
/* CTA секция */
|
||||
@@ -986,47 +1014,47 @@ export default {
|
||||
.hero-title {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
|
||||
.hero-features {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.hero-actions {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
.schedule-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
.workouts-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
.locations-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
.cta-features {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
.cta-actions {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
|
||||
.modal-content {
|
||||
padding: 2rem 1.5rem;
|
||||
}
|
||||
@@ -1036,15 +1064,15 @@ export default {
|
||||
.hero-section {
|
||||
padding: 80px 0 60px;
|
||||
}
|
||||
|
||||
|
||||
.hero-title {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
|
||||
.section-title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
|
||||
.container {
|
||||
padding: 0 15px;
|
||||
}
|
||||
@@ -1056,6 +1084,7 @@ export default {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
|
||||
@@ -2,7 +2,6 @@ package handlers
|
||||
|
||||
import (
|
||||
"api_bb/internal/models"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Общая функция для преобразования User в UserResponse
|
||||
@@ -21,8 +20,3 @@ func toUserResponse(user *models.User) UserResponse {
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// Обработчик для OPTIONS запросов
|
||||
func (h *UserHandler) handleOptions(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
@@ -19,14 +19,14 @@ import (
|
||||
)
|
||||
|
||||
type UserHandler struct {
|
||||
authService service.AuthService
|
||||
logger logger.Interface
|
||||
userService service.UserService
|
||||
}
|
||||
|
||||
func NewUserHandler(authService service.AuthService) *UserHandler {
|
||||
func NewUserHandler(userService service.UserService) *UserHandler {
|
||||
return &UserHandler{
|
||||
authService: authService,
|
||||
logger: logger.NewWrapper(logger.Get().With(zap.String("handler", "user"))),
|
||||
userService: userService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ func (h *UserHandler) UpdateProfile(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Сохраняем обновленные данные
|
||||
if err := h.authService.UpdateProfile(updatedUser); err != nil {
|
||||
if err := h.userService.UpdateProfile(updatedUser); err != nil {
|
||||
h.logger.Error("failed to update profile in service",
|
||||
zap.Uint("user_id", currentUser.ID),
|
||||
zap.Error(err),
|
||||
|
||||
@@ -32,11 +32,12 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler {
|
||||
// Initialize services with logger
|
||||
jwtService := service.NewJWTService(config.JWTSecret)
|
||||
authService := service.NewAuthService(userRepo, jwtService, baseLogger) // Передаем логгер
|
||||
userService := service.NewUserService(userRepo, jwtService, baseLogger)
|
||||
|
||||
// Initialize handlers
|
||||
healthHandler := handlers.NewHealthHandler()
|
||||
authHandler := handlers.NewAuthHandler(authService, jwtService)
|
||||
userHandler := handlers.NewUserHandler(authService)
|
||||
userHandler := handlers.NewUserHandler(&userService)
|
||||
|
||||
// Health routes
|
||||
r.Mount("/api", healthHandler.Routes())
|
||||
|
||||
@@ -15,8 +15,6 @@ import (
|
||||
type AuthService interface {
|
||||
Register(user *models.User) error
|
||||
Login(email, password string) (*models.User, string, error)
|
||||
GetUserProfile(userID uint) (*models.User, error)
|
||||
UpdateProfile(user *models.User) error
|
||||
}
|
||||
|
||||
type authService struct {
|
||||
|
||||
@@ -21,11 +21,17 @@ type userService struct {
|
||||
logger logger.Interface
|
||||
}
|
||||
|
||||
func NewUserService(userRepo repository.UserRepository, jwtService JWTService, log logger.Interface) AuthService {
|
||||
// Создаем логгер с контекстом для сервиса
|
||||
serviceLogger := log.With(zap.String("service", "auth"))
|
||||
|
||||
return &authService{
|
||||
// UpdateProfile implements UserService.
|
||||
func (s userService) UpdateProfile(user *models.User) error {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func NewUserService(userRepo repository.UserRepository, jwtService JWTService, log logger.Interface) userService {
|
||||
// Создаем логгер с контекстом для сервиса
|
||||
serviceLogger := log.With(zap.String("service", "user"))
|
||||
|
||||
return userService{
|
||||
userRepo: userRepo,
|
||||
jwtService: jwtService,
|
||||
logger: serviceLogger,
|
||||
@@ -64,7 +70,7 @@ func (s *authService) UpdateProfile(user *models.User) error {
|
||||
return s.userRepo.UpdateExcludeEmail(updateData)
|
||||
}
|
||||
|
||||
func (s *authService) GetUserProfile(userID uint) (*models.User, error) {
|
||||
func (s *userService) GetUserProfile(userID uint) (*models.User, error) {
|
||||
s.logger.Debug("Getting user profile",
|
||||
zap.Uint("user_id", userID),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user