Some checks failed
Deploy Development / deploy (push) Failing after 4s
Docker & Deployment: - docker-compose.yml (Prod: Port 3003/8003) - docker-compose.dev-env.yml (Dev: Port 3098/8098) - Backend Dockerfile (Python 3.12-slim) - Frontend Dockerfile (Node 20 + Nginx) - Gitea Actions (deploy-dev.yml, deploy-prod.yml) Frontend: - React 18 + Vite setup - package.json, vite.config.js, index.html - App.jsx (minimal with version display) - api.js (complete API client) - app.css + AuthContext from Mitai - main.jsx entry point Backend Migrations: - 001_auth_membership.sql (Auth + Features + Tier Limits) - 002_organization.sql (Clubs, Divisions, Training Groups) - 003_catalogs.sql (Skills + Methods with sample data) Documentation: - .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.) - SHINKAN_PROJECT_SETUP.md (technical setup guide) Server: - Directories created on Pi: /home/lars/docker/shinkan[-dev] - Gitea Runner configured and running Ready for first deployment to dev.shinkan.jinkendo.de version: 0.1.0 date: 2026-04-21
134 lines
3.8 KiB
JavaScript
134 lines
3.8 KiB
JavaScript
import { createContext, useContext, useState, useEffect } from 'react'
|
|
|
|
const AuthContext = createContext(null)
|
|
|
|
const TOKEN_KEY = 'bodytrack_token'
|
|
const PROFILE_KEY = 'bodytrack_active_profile'
|
|
|
|
export function AuthProvider({ children }) {
|
|
const [session, setSession] = useState(null) // {token, profile_id, role}
|
|
const [loading, setLoading] = useState(true)
|
|
const [needsSetup, setNeedsSetup] = useState(false)
|
|
|
|
useEffect(() => {
|
|
checkStatus()
|
|
}, [])
|
|
|
|
const checkStatus = async () => {
|
|
try {
|
|
const r = await fetch('/api/auth/status')
|
|
const status = await r.json()
|
|
|
|
if (status.needs_setup) {
|
|
setNeedsSetup(true)
|
|
setLoading(false)
|
|
return
|
|
}
|
|
|
|
// Try existing token
|
|
const token = localStorage.getItem(TOKEN_KEY)
|
|
if (token) {
|
|
const me = await fetch('/api/auth/me', {
|
|
headers: { 'X-Auth-Token': token }
|
|
})
|
|
if (me.ok) {
|
|
const profile = await me.json()
|
|
setSession({ token, profile_id: profile.id, role: profile.role, profile })
|
|
setLoading(false)
|
|
return
|
|
}
|
|
// Token expired
|
|
localStorage.removeItem(TOKEN_KEY)
|
|
}
|
|
} catch(e) {
|
|
console.error('Auth check failed', e)
|
|
}
|
|
setLoading(false)
|
|
}
|
|
|
|
const login = async (credentials) => {
|
|
// Support both new {email, pin} and legacy {profile_id, pin}
|
|
const body = typeof credentials === 'object' ? credentials : { profile_id: credentials }
|
|
const r = await fetch('/api/auth/login', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(body)
|
|
})
|
|
if (!r.ok) {
|
|
const err = await r.json()
|
|
throw new Error(err.detail || 'Login fehlgeschlagen')
|
|
}
|
|
const data = await r.json()
|
|
localStorage.setItem(TOKEN_KEY, data.token)
|
|
localStorage.setItem(PROFILE_KEY, data.profile_id)
|
|
// Fetch full profile
|
|
const me = await fetch('/api/auth/me', { headers: { 'X-Auth-Token': data.token } })
|
|
const profile = await me.json()
|
|
setSession({ token: data.token, profile_id: data.profile_id, role: data.role, profile })
|
|
return data
|
|
}
|
|
|
|
const setup = async (formData) => {
|
|
const r = await fetch('/api/auth/setup', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(formData)
|
|
})
|
|
if (!r.ok) {
|
|
const err = await r.json()
|
|
throw new Error(err.detail || 'Setup fehlgeschlagen')
|
|
}
|
|
const data = await r.json()
|
|
localStorage.setItem(TOKEN_KEY, data.token)
|
|
localStorage.setItem(PROFILE_KEY, data.profile_id)
|
|
setNeedsSetup(false)
|
|
await checkStatus()
|
|
return data
|
|
}
|
|
|
|
const setAuthFromToken = (token, profile) => {
|
|
// Direct token/profile set (for email verification auto-login)
|
|
localStorage.setItem(TOKEN_KEY, token)
|
|
localStorage.setItem(PROFILE_KEY, profile.id)
|
|
setSession({
|
|
token,
|
|
profile_id: profile.id,
|
|
role: profile.role || 'user',
|
|
profile
|
|
})
|
|
}
|
|
|
|
const logout = async () => {
|
|
const token = localStorage.getItem(TOKEN_KEY)
|
|
if (token) {
|
|
await fetch('/api/auth/logout', { method: 'POST', headers: { 'X-Auth-Token': token } })
|
|
}
|
|
localStorage.removeItem(TOKEN_KEY)
|
|
setSession(null)
|
|
}
|
|
|
|
const isAdmin = session?.role === 'admin'
|
|
const canUseAI = session?.profile?.ai_enabled !== 0
|
|
const canExport = session?.profile?.export_enabled !== 0
|
|
|
|
return (
|
|
<AuthContext.Provider value={{
|
|
session, loading, needsSetup,
|
|
login, setup, logout, setAuthFromToken,
|
|
isAdmin, canUseAI, canExport,
|
|
token: session?.token,
|
|
profileId: session?.profile_id,
|
|
}}>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
)
|
|
}
|
|
|
|
export function useAuth() {
|
|
return useContext(AuthContext)
|
|
}
|
|
|
|
export function getToken() {
|
|
return localStorage.getItem(TOKEN_KEY)
|
|
}
|