From ee8d79d155db2d59da7ea229d8de2d0e38a3d5c9 Mon Sep 17 00:00:00 2001 From: "mamonov.ep" Date: Fri, 19 Dec 2025 20:17:52 +0300 Subject: [PATCH] Redesign UI with Vuetify and improve configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major changes: - Full UI redesign with Vuetify 3 (dark theme, modern components) - Sidebar navigation with gradient logo - Redesigned player controls with Material Design icons - New room cards, track lists, and filter UI with chips - Modern auth pages with centered cards Configuration improvements: - Centralized all settings in root .env file - Removed redundant backend/.env and frontend/.env files - Increased file upload limit to 100MB (nginx + backend) - Added build args for Vite environment variables - Frontend now uses relative paths (better for domain deployment) UI Components updated: - App.vue: v-navigation-drawer with sidebar - MiniPlayer: v-footer with modern controls - Queue: v-list with styled items - RoomView: improved filters with clickable chips - All views: Vuetify cards, buttons, text fields 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .env.example | 8 +- backend/.env.example | 17 - backend/app/config.py | 2 +- backend/app/routers/rooms.py | 38 +- backend/app/schemas/room.py | 13 + docker-compose.yml | 7 +- frontend/.env.example | 3 - frontend/Dockerfile | 6 + frontend/nginx.conf | 5 +- frontend/package.json | 7 +- frontend/src/App.vue | 182 ++++++- frontend/src/components/chat/ChatWindow.vue | 61 ++- frontend/src/components/player/MiniPlayer.vue | 493 ++++++++---------- .../src/components/room/ParticipantsList.vue | 67 +-- frontend/src/components/room/Queue.vue | 140 ++--- frontend/src/components/room/RoomCard.vue | 133 +++-- frontend/src/components/tracks/TrackItem.vue | 12 +- frontend/src/components/tracks/TrackList.vue | 5 + frontend/src/main.js | 2 + frontend/src/plugins/vuetify.js | 29 ++ frontend/src/views/HomeView.vue | 137 ++++- frontend/src/views/LoginView.vue | 114 ++-- frontend/src/views/RegisterView.vue | 116 +++-- frontend/src/views/RoomView.vue | 467 +++++++++-------- frontend/src/views/TracksView.vue | 261 +++++++--- frontend/vite.config.js | 6 +- 26 files changed, 1498 insertions(+), 833 deletions(-) delete mode 100644 backend/.env.example delete mode 100644 frontend/.env.example create mode 100644 frontend/src/plugins/vuetify.js diff --git a/.env.example b/.env.example index 4950136..8cb46de 100644 --- a/.env.example +++ b/.env.example @@ -14,5 +14,11 @@ S3_BUCKET_NAME=enigfm S3_REGION=ru-1 # Limits -MAX_FILE_SIZE_MB=10 +MAX_FILE_SIZE_MB=100 MAX_STORAGE_GB=90 +MAX_ROOM_PARTICIPANTS=50 + +# Frontend (Vite) +# VITE_API_URL - оставляем пустым для использования относительных путей +# VITE_WS_URL - оставляем пустым для автоопределения +VITE_MAX_FILE_SIZE_MB=100 diff --git a/backend/.env.example b/backend/.env.example deleted file mode 100644 index 847e307..0000000 --- a/backend/.env.example +++ /dev/null @@ -1,17 +0,0 @@ -# Database -DATABASE_URL=postgresql://postgres:postgres@localhost:4002/enigfm - -# JWT -SECRET_KEY=your-secret-key-change-in-production - -# S3 (FirstVDS) -S3_ENDPOINT_URL=https://s3.firstvds.ru -S3_ACCESS_KEY=your-access-key -S3_SECRET_KEY=your-secret-key -S3_BUCKET_NAME=enigfm -S3_REGION=ru-1 - -# Limits -MAX_FILE_SIZE_MB=10 -MAX_STORAGE_GB=90 -MAX_ROOM_PARTICIPANTS=50 diff --git a/backend/app/config.py b/backend/app/config.py index d5a585d..0dc9952 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -24,7 +24,7 @@ class Settings(BaseSettings): max_room_participants: int = 50 class Config: - env_file = ".env" + # env_file не нужен - переменные передаются через docker-compose extra = "ignore" diff --git a/backend/app/routers/rooms.py b/backend/app/routers/rooms.py index 2033dbc..629949a 100644 --- a/backend/app/routers/rooms.py +++ b/backend/app/routers/rooms.py @@ -7,7 +7,7 @@ from ..database import get_db from ..models.user import User from ..models.room import Room, RoomParticipant from ..models.track import RoomQueue -from ..schemas.room import RoomCreate, RoomResponse, RoomDetailResponse, QueueAdd, QueueAddMultiple +from ..schemas.room import RoomCreate, RoomResponse, RoomDetailResponse, QueueAdd, QueueAddMultiple, QueueItemResponse from ..schemas.track import TrackResponse from ..schemas.user import UserResponse from ..services.auth import get_current_user @@ -178,7 +178,7 @@ async def leave_room( return {"status": "left"} -@router.get("/{room_id}/queue", response_model=list[TrackResponse]) +@router.get("/{room_id}/queue", response_model=list[QueueItemResponse]) async def get_queue(room_id: UUID, db: AsyncSession = Depends(get_db)): result = await db.execute( select(RoomQueue) @@ -187,7 +187,13 @@ async def get_queue(room_id: UUID, db: AsyncSession = Depends(get_db)): .order_by(RoomQueue.position) ) queue_items = result.scalars().all() - return [TrackResponse.model_validate(item.track) for item in queue_items] + return [ + QueueItemResponse( + track=TrackResponse.model_validate(item.track), + added_by=item.added_by + ) + for item in queue_items + ] @router.post("/{room_id}/queue") @@ -314,13 +320,25 @@ async def remove_from_queue( ) queue_item = result.scalar_one_or_none() - if queue_item: - await db.delete(queue_item) - - # Notify others - await manager.broadcast_to_room( - room_id, - {"type": "queue_updated"}, + if not queue_item: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Track not in queue" ) + # Check if user added this track to queue + if queue_item.added_by != current_user.id: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Can only remove tracks you added" + ) + + await db.delete(queue_item) + + # Notify others + await manager.broadcast_to_room( + room_id, + {"type": "queue_updated"}, + ) + return {"status": "removed"} diff --git a/backend/app/schemas/room.py b/backend/app/schemas/room.py index 069b49e..199a98e 100644 --- a/backend/app/schemas/room.py +++ b/backend/app/schemas/room.py @@ -49,3 +49,16 @@ class QueueAdd(BaseModel): class QueueAddMultiple(BaseModel): track_ids: list[UUID] + + +class QueueItemResponse(BaseModel): + track: "TrackResponse" + added_by: UUID + + class Config: + from_attributes = True + + +# Import at the end to avoid circular imports +from .track import TrackResponse +QueueItemResponse.model_rebuild() diff --git a/docker-compose.yml b/docker-compose.yml index af66655..9376af5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,7 +28,10 @@ services: S3_ACCESS_KEY: ${S3_ACCESS_KEY} S3_SECRET_KEY: ${S3_SECRET_KEY} S3_BUCKET_NAME: ${S3_BUCKET_NAME:-enigfm} - S3_REGION: ${S3_REGION:-ru-1} + S3_REGION: ${S3_REGION:-default} + MAX_FILE_SIZE_MB: ${MAX_FILE_SIZE_MB:-10} + MAX_STORAGE_GB: ${MAX_STORAGE_GB:-90} + MAX_ROOM_PARTICIPANTS: ${MAX_ROOM_PARTICIPANTS:-50} ports: - "4001:8000" depends_on: @@ -40,6 +43,8 @@ services: build: context: ./frontend dockerfile: Dockerfile + args: + VITE_MAX_FILE_SIZE_MB: ${VITE_MAX_FILE_SIZE_MB:-100} ports: - "4000:80" depends_on: diff --git a/frontend/.env.example b/frontend/.env.example deleted file mode 100644 index f5dabbb..0000000 --- a/frontend/.env.example +++ /dev/null @@ -1,3 +0,0 @@ -VITE_API_URL=http://localhost:4001 -VITE_WS_URL=ws://localhost:4001 -VITE_MAX_FILE_SIZE_MB=10 diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 9d435a8..c308d7b 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -3,6 +3,12 @@ FROM node:20-alpine as build WORKDIR /app +# Build arguments (from docker-compose) +ARG VITE_MAX_FILE_SIZE_MB + +# Set as env variables for Vite build +ENV VITE_MAX_FILE_SIZE_MB=${VITE_MAX_FILE_SIZE_MB} + COPY package*.json ./ RUN npm install diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 4237727..45a8c43 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -5,7 +5,7 @@ server { index index.html; # Max upload size - client_max_body_size 30M; + client_max_body_size 100M; # Gzip compression gzip on; @@ -27,6 +27,9 @@ server { proxy_buffering off; proxy_cache off; proxy_request_buffering off; + + # Max upload size for API + client_max_body_size 100M; } # WebSocket proxy diff --git a/frontend/package.json b/frontend/package.json index 8fa2f58..7c28355 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,10 +12,13 @@ "vue": "^3.4.15", "vue-router": "^4.2.5", "pinia": "^2.1.7", - "axios": "^1.6.5" + "axios": "^1.6.5", + "vuetify": "^3.5.0", + "@mdi/font": "^7.4.47" }, "devDependencies": { "@vitejs/plugin-vue": "^5.0.3", - "vite": "^5.0.11" + "vite": "^5.0.11", + "vite-plugin-vuetify": "^2.0.1" } } diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 85c2c40..dc771fd 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,37 +1,187 @@ diff --git a/frontend/src/components/chat/ChatWindow.vue b/frontend/src/components/chat/ChatWindow.vue index 4ed240f..eb4541b 100644 --- a/frontend/src/components/chat/ChatWindow.vue +++ b/frontend/src/components/chat/ChatWindow.vue @@ -1,23 +1,38 @@ @@ -74,12 +89,7 @@ function scrollToBottom() { .chat { display: flex; flex-direction: column; - height: 400px; -} - -.chat h3 { - margin: 0 0 12px 0; - font-size: 16px; + height: 450px; } .messages { @@ -88,20 +98,19 @@ function scrollToBottom() { display: flex; flex-direction: column; gap: 8px; - padding-right: 8px; + padding: 16px; +} + +.empty-chat { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + text-align: center; } .chat-input { - display: flex; - gap: 8px; - margin-top: 12px; -} - -.chat-input input { - flex: 1; -} - -.chat-input button { - white-space: nowrap; + padding: 16px; } diff --git a/frontend/src/components/player/MiniPlayer.vue b/frontend/src/components/player/MiniPlayer.vue index aca00cb..ce32a57 100644 --- a/frontend/src/components/player/MiniPlayer.vue +++ b/frontend/src/components/player/MiniPlayer.vue @@ -1,76 +1,136 @@ diff --git a/frontend/src/components/room/Queue.vue b/frontend/src/components/room/Queue.vue index 803b80b..209dddf 100644 --- a/frontend/src/components/room/Queue.vue +++ b/frontend/src/components/room/Queue.vue @@ -1,36 +1,67 @@