diff --git a/backend/app/api/endpoints/auth.py b/backend/app/api/endpoints/auth.py
index 956431d..fd237e8 100644
--- a/backend/app/api/endpoints/auth.py
+++ b/backend/app/api/endpoints/auth.py
@@ -7,6 +7,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
from app.core.security import hash_password, verify_password, create_access_token, get_current_user
from app.models.user import User
+from pydantic import BaseModel, EmailStr
from app.schemas.auth import RegisterRequest, LoginRequest, TokenResponse, UserResponse
from app.services.spotify import get_spotify_auth_url, exchange_spotify_code, get_spotify_user
@@ -29,6 +30,42 @@ async def register(data: RegisterRequest, db: AsyncSession = Depends(get_db)):
return TokenResponse(access_token=create_access_token(user.id))
+class UpdateProfileRequest(BaseModel):
+ name: str | None = None
+ email: EmailStr | None = None
+
+
+class ChangePasswordRequest(BaseModel):
+ current_password: str
+ new_password: str
+
+
+@router.put("/me")
+async def update_profile(data: UpdateProfileRequest, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
+ if data.name:
+ user.name = data.name
+ if data.email and data.email != user.email:
+ existing = await db.execute(select(User).where(User.email == data.email))
+ if existing.scalar_one_or_none():
+ raise HTTPException(status_code=400, detail="Email already in use")
+ user.email = data.email
+ return UserResponse(id=user.id, email=user.email, name=user.name, is_pro=user.is_pro, spotify_connected=user.spotify_id is not None)
+
+
+@router.post("/change-password")
+async def change_password(data: ChangePasswordRequest, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
+ if not user.hashed_password or not verify_password(data.current_password, user.hashed_password):
+ raise HTTPException(status_code=400, detail="Current password is incorrect")
+ user.hashed_password = hash_password(data.new_password)
+ return {"ok": True}
+
+
+@router.delete("/me")
+async def delete_account(user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
+ await db.delete(user)
+ return {"ok": True}
+
+
@router.post("/login", response_model=TokenResponse)
async def login(data: LoginRequest, db: AsyncSession = Depends(get_db)):
result = await db.execute(select(User).where(User.email == data.email))
diff --git a/frontend/index.html b/frontend/index.html
index dcd0bb4..38dac03 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -22,6 +22,10 @@
+
+
+
+
Vynl - AI Music Discovery
diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json
new file mode 100644
index 0000000..c58a25f
--- /dev/null
+++ b/frontend/public/manifest.json
@@ -0,0 +1,12 @@
+{
+ "name": "Vynl - AI Music Discovery",
+ "short_name": "Vynl",
+ "description": "Discover music you'll love with AI",
+ "start_url": "/dashboard",
+ "display": "standalone",
+ "background_color": "#FFF7ED",
+ "theme_color": "#7C3AED",
+ "icons": [
+ { "src": "/favicon.svg", "sizes": "any", "type": "image/svg+xml" }
+ ]
+}
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 8e8e24a..8f3dad7 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -21,6 +21,7 @@ import Timeline from './pages/Timeline'
import Compatibility from './pages/Compatibility'
import CrateDigger from './pages/CrateDigger'
import RabbitHole from './pages/RabbitHole'
+import Settings from './pages/Settings'
function RootRedirect() {
const { user, loading } = useAuth()
@@ -202,6 +203,16 @@ function AppRoutes() {
}
/>
+
+
+
+
+
+ }
+ />
} />
} />
diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx
index c1d487b..2d9631c 100644
--- a/frontend/src/components/Layout.tsx
+++ b/frontend/src/components/Layout.tsx
@@ -1,6 +1,6 @@
import { useState } from 'react'
import { Link, useLocation, useNavigate } from 'react-router-dom'
-import { Disc3, LayoutDashboard, Fingerprint, Clock, ListMusic, ListPlus, Compass, Lightbulb, Store, Users, ArrowDownCircle, Heart, Crown, Shield, Menu, X, LogOut, User } from 'lucide-react'
+import { Disc3, LayoutDashboard, Fingerprint, Clock, ListMusic, ListPlus, Compass, Lightbulb, Store, Users, ArrowDownCircle, Heart, Crown, Shield, Menu, X, LogOut, User, Settings } from 'lucide-react'
import { useAuth } from '../lib/auth'
const ADMIN_EMAIL = 'chris.ryan@deepcutsai.com'
@@ -96,6 +96,14 @@ export default function Layout({ children }: { children: React.ReactNode }) {
{user?.name}
{user?.email}
+ setUserMenuOpen(false)}
+ className="w-full flex items-center gap-2 px-4 py-2 text-sm text-charcoal hover:bg-purple-50 transition-colors no-underline"
+ >
+
+ Settings
+