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 |
@@ -31,16 +31,11 @@
|
|||||||
|
|
||||||
<div class="schedule-container">
|
<div class="schedule-container">
|
||||||
<div class="schedule-grid">
|
<div class="schedule-grid">
|
||||||
<div
|
<div v-for="day in schedule" :key="day.name" class="schedule-day" :class="{
|
||||||
v-for="day in schedule"
|
'training-day': day.time !== '--:--',
|
||||||
:key="day.name"
|
'rest-day': day.time === '--:--',
|
||||||
class="schedule-day"
|
'today': isToday(day.name)
|
||||||
:class="{
|
}">
|
||||||
'training-day': day.time !== '--:--',
|
|
||||||
'rest-day': day.time === '--:--',
|
|
||||||
'today': isToday(day.name)
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div class="day-header">
|
<div class="day-header">
|
||||||
<h4>{{ day.name }}</h4>
|
<h4>{{ day.name }}</h4>
|
||||||
<span class="day-badge" v-if="isToday(day.name)">Сегодня</span>
|
<span class="day-badge" v-if="isToday(day.name)">Сегодня</span>
|
||||||
@@ -55,19 +50,11 @@
|
|||||||
📍 {{ day.location }}
|
📍 {{ day.location }}
|
||||||
</div>
|
</div>
|
||||||
<div class="day-features" v-if="day.features">
|
<div class="day-features" v-if="day.features">
|
||||||
<span
|
<span v-for="feature in day.features" :key="feature" class="feature-tag">
|
||||||
v-for="feature in day.features"
|
|
||||||
:key="feature"
|
|
||||||
class="feature-tag"
|
|
||||||
>
|
|
||||||
{{ feature }}
|
{{ feature }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button v-if="day.time !== '--:--'" class="btn-day" @click="openTrainingModal(day)">
|
||||||
v-if="day.time !== '--:--'"
|
|
||||||
class="btn-day"
|
|
||||||
@click="openTrainingModal(day)"
|
|
||||||
>
|
|
||||||
🏃 Записаться
|
🏃 Записаться
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -150,7 +137,9 @@
|
|||||||
<div class="locations-grid">
|
<div class="locations-grid">
|
||||||
<div class="location-card">
|
<div class="location-card">
|
||||||
<div class="location-image">
|
<div class="location-image">
|
||||||
<div class="image-placeholder">🏞️</div>
|
<div class="image-placeholder">
|
||||||
|
<img :src="getImageUrl('locations/riverSide.jpeg')" alt="Набережная Уфа Монумент">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="location-info">
|
<div class="location-info">
|
||||||
<h3>Набережная</h3>
|
<h3>Набережная</h3>
|
||||||
@@ -165,7 +154,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="location-card">
|
<div class="location-card">
|
||||||
<div class="location-image">
|
<div class="location-image">
|
||||||
<div class="image-placeholder">🏟️</div>
|
<div class="image-placeholder">
|
||||||
|
<img :src="getImageUrl('locations/dinamo.jpg')" alt="Стадион Динамо">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="location-info">
|
<div class="location-info">
|
||||||
<h3>Стадион "Динамо"</h3>
|
<h3>Стадион "Динамо"</h3>
|
||||||
@@ -178,6 +169,23 @@
|
|||||||
<p class="location-desc">Лучшее место для работы над скоростью и техникой</p>
|
<p class="location-desc">Лучшее место для работы над скоростью и техникой</p>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -240,23 +248,11 @@
|
|||||||
<form class="signup-form" @submit.prevent="handleSignup">
|
<form class="signup-form" @submit.prevent="handleSignup">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="name">Ваше имя *</label>
|
<label for="name">Ваше имя *</label>
|
||||||
<input
|
<input id="name" v-model="signupForm.name" type="text" required placeholder="Введите ваше имя">
|
||||||
id="name"
|
|
||||||
v-model="signupForm.name"
|
|
||||||
type="text"
|
|
||||||
required
|
|
||||||
placeholder="Введите ваше имя"
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="phone">Телефон *</label>
|
<label for="phone">Телефон *</label>
|
||||||
<input
|
<input id="phone" v-model="signupForm.phone" type="tel" required placeholder="+7 (999) 123-45-67">
|
||||||
id="phone"
|
|
||||||
v-model="signupForm.phone"
|
|
||||||
type="tel"
|
|
||||||
required
|
|
||||||
placeholder="+7 (999) 123-45-67"
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="level">Уровень подготовки</label>
|
<label for="level">Уровень подготовки</label>
|
||||||
@@ -322,6 +318,7 @@ export default {
|
|||||||
name: 'Пятница',
|
name: 'Пятница',
|
||||||
time: '--:--',
|
time: '--:--',
|
||||||
activity: 'Восстановление',
|
activity: 'Восстановление',
|
||||||
|
location: 'Свободное',
|
||||||
features: ['Самостоятельно', 'Йога/Плавание']
|
features: ['Самостоятельно', 'Йога/Плавание']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -335,6 +332,7 @@ export default {
|
|||||||
name: 'Воскресенье',
|
name: 'Воскресенье',
|
||||||
time: '--:--',
|
time: '--:--',
|
||||||
activity: 'Восстановление',
|
activity: 'Восстановление',
|
||||||
|
location: 'Свободное',
|
||||||
features: ['Полный отдых', 'Восстановительные процедуры']
|
features: ['Полный отдых', 'Восстановительные процедуры']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -347,6 +345,13 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getImageUrl(path) {
|
||||||
|
// В продакшене замените на правильный путь
|
||||||
|
const baseUrl = import.meta.env.BASE_URL
|
||||||
|
// Путь от корня public/
|
||||||
|
console.log(`${baseUrl}images/${path}`)
|
||||||
|
return `${baseUrl}images/${path}`
|
||||||
|
},
|
||||||
isToday(dayName) {
|
isToday(dayName) {
|
||||||
return dayName === this.today
|
return dayName === this.today
|
||||||
},
|
},
|
||||||
@@ -700,6 +705,9 @@ export default {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.08);
|
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.08);
|
||||||
transition: transform 0.3s ease;
|
transition: transform 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.location-card:hover {
|
.location-card:hover {
|
||||||
@@ -708,24 +716,42 @@ export default {
|
|||||||
|
|
||||||
.location-image {
|
.location-image {
|
||||||
height: 200px;
|
height: 200px;
|
||||||
background: linear-gradient(135deg, #2e8b57 0%, #26734a 100%);
|
overflow: hidden;
|
||||||
display: flex;
|
position: relative;
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-placeholder {
|
.image-placeholder {
|
||||||
font-size: 4rem;
|
width: 100%;
|
||||||
color: white;
|
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 {
|
.location-info {
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.location-info h3 {
|
.location-info h3 {
|
||||||
color: #2e8b57;
|
color: #2e8b57;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
|
font-size: 1.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.location-address {
|
.location-address {
|
||||||
@@ -747,11 +773,13 @@ export default {
|
|||||||
padding: 0.3rem 0.8rem;
|
padding: 0.3rem 0.8rem;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.location-desc {
|
.location-desc {
|
||||||
color: #666;
|
color: #666;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
margin-top: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CTA секция */
|
/* CTA секция */
|
||||||
@@ -1056,6 +1084,7 @@ export default {
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(30px);
|
transform: translateY(30px);
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"api_bb/internal/models"
|
"api_bb/internal/models"
|
||||||
"net/http"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Общая функция для преобразования User в UserResponse
|
// Общая функция для преобразования User в UserResponse
|
||||||
@@ -21,8 +20,3 @@ func toUserResponse(user *models.User) UserResponse {
|
|||||||
UpdatedAt: user.UpdatedAt,
|
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 {
|
type UserHandler struct {
|
||||||
authService service.AuthService
|
|
||||||
logger logger.Interface
|
logger logger.Interface
|
||||||
|
userService service.UserService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUserHandler(authService service.AuthService) *UserHandler {
|
func NewUserHandler(userService service.UserService) *UserHandler {
|
||||||
return &UserHandler{
|
return &UserHandler{
|
||||||
authService: authService,
|
|
||||||
logger: logger.NewWrapper(logger.Get().With(zap.String("handler", "user"))),
|
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",
|
h.logger.Error("failed to update profile in service",
|
||||||
zap.Uint("user_id", currentUser.ID),
|
zap.Uint("user_id", currentUser.ID),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
|
|||||||
@@ -32,11 +32,12 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler {
|
|||||||
// Initialize services with logger
|
// Initialize services with logger
|
||||||
jwtService := service.NewJWTService(config.JWTSecret)
|
jwtService := service.NewJWTService(config.JWTSecret)
|
||||||
authService := service.NewAuthService(userRepo, jwtService, baseLogger) // Передаем логгер
|
authService := service.NewAuthService(userRepo, jwtService, baseLogger) // Передаем логгер
|
||||||
|
userService := service.NewUserService(userRepo, jwtService, baseLogger)
|
||||||
|
|
||||||
// Initialize handlers
|
// Initialize handlers
|
||||||
healthHandler := handlers.NewHealthHandler()
|
healthHandler := handlers.NewHealthHandler()
|
||||||
authHandler := handlers.NewAuthHandler(authService, jwtService)
|
authHandler := handlers.NewAuthHandler(authService, jwtService)
|
||||||
userHandler := handlers.NewUserHandler(authService)
|
userHandler := handlers.NewUserHandler(&userService)
|
||||||
|
|
||||||
// Health routes
|
// Health routes
|
||||||
r.Mount("/api", healthHandler.Routes())
|
r.Mount("/api", healthHandler.Routes())
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ import (
|
|||||||
type AuthService interface {
|
type AuthService interface {
|
||||||
Register(user *models.User) error
|
Register(user *models.User) error
|
||||||
Login(email, password string) (*models.User, string, error)
|
Login(email, password string) (*models.User, string, error)
|
||||||
GetUserProfile(userID uint) (*models.User, error)
|
|
||||||
UpdateProfile(user *models.User) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type authService struct {
|
type authService struct {
|
||||||
|
|||||||
@@ -21,11 +21,17 @@ type userService struct {
|
|||||||
logger logger.Interface
|
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,
|
userRepo: userRepo,
|
||||||
jwtService: jwtService,
|
jwtService: jwtService,
|
||||||
logger: serviceLogger,
|
logger: serviceLogger,
|
||||||
@@ -64,7 +70,7 @@ func (s *authService) UpdateProfile(user *models.User) error {
|
|||||||
return s.userRepo.UpdateExcludeEmail(updateData)
|
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",
|
s.logger.Debug("Getting user profile",
|
||||||
zap.Uint("user_id", userID),
|
zap.Uint("user_id", userID),
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user