Files
leopost-full/frontend/src/AuthContext.jsx
Michele 77ca70cd48 feat: multi-user SaaS, piani Freemium/Pro, Google OAuth, admin panel
BLOCCO 1 - Multi-user data model:
- User: email, display_name, avatar_url, auth_provider, google_id
- User: subscription_plan, subscription_expires_at, is_admin, post counters
- SubscriptionCode table per redeem codes
- user_id FK su Character, Post, AffiliateLink, EditorialPlan, SocialAccount, SystemSetting
- Migrazione SQLite-safe (ALTER TABLE) + preserva dati esistenti

BLOCCO 2 - Auth completo:
- Registrazione email/password + login multi-user
- Google OAuth 2.0 (httpx, no deps esterne)
- Callback flow: Google -> /auth/callback?token=JWT -> frontend
- Backward compat login admin con username

BLOCCO 3 - Piani e abbonamenti:
- Freemium: 1 character, 15 post/mese, FB+IG only, no auto-plans
- Pro: illimitato, tutte le piattaforme, tutte le feature
- Enforcement automatico in tutti i router
- Redeem codes con durate 1/3/6/12 mesi
- Admin panel: genera codici, lista utenti

BLOCCO 4 - Frontend completo:
- Login page design Leopost (split coral/cream, Google, social coming soon)
- AuthCallback per OAuth redirect
- PlanBanner, UpgradeModal con pricing
- AdminSettings per generazione codici
- CharacterForm con tab Account Social + guide setup

Deploy:
- Dockerfile con ARG VITE_BASE_PATH/VITE_API_BASE
- docker-compose.prod.yml per leopost.it (no subpath)
- docker-compose.yml aggiornato per lab

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 20:01:07 +02:00

111 lines
2.6 KiB
JavaScript

import { createContext, useContext, useState, useEffect } from 'react'
import { api } from './api'
const AuthContext = createContext(null)
const PLAN_LIMITS = {
freemium: {
characters: 1,
posts: 15,
platforms: ['facebook', 'instagram'],
auto_plans: false,
comments_management: false,
affiliate_links: false,
},
pro: {
characters: null,
posts: null,
platforms: ['facebook', 'instagram', 'youtube', 'tiktok'],
auto_plans: true,
comments_management: true,
affiliate_links: true,
},
}
function computeIsPro(user) {
if (!user) return false
if (user.subscription_plan !== 'pro') return false
if (user.subscription_expires_at) {
return new Date(user.subscription_expires_at) > new Date()
}
return true
}
export function AuthProvider({ children }) {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
const loadUser = async () => {
try {
const data = await api.get('/auth/me')
setUser(data)
return data
} catch {
localStorage.removeItem('token')
setUser(null)
return null
}
}
useEffect(() => {
const token = localStorage.getItem('token')
if (token) {
loadUser().finally(() => setLoading(false))
} else {
setLoading(false)
}
}, [])
const login = async (emailOrUsername, password) => {
// Try email login first, fall back to username
const isEmail = emailOrUsername.includes('@')
const body = isEmail
? { email: emailOrUsername, password }
: { username: emailOrUsername, password }
const data = await api.post('/auth/login', body)
localStorage.setItem('token', data.access_token)
setUser(data.user)
return data.user
}
const register = async (email, password, displayName) => {
const data = await api.post('/auth/register', { email, password, display_name: displayName })
localStorage.setItem('token', data.access_token)
setUser(data.user)
return data.user
}
const loginWithToken = (token) => {
localStorage.setItem('token', token)
loadUser()
}
const logout = () => {
localStorage.removeItem('token')
setUser(null)
}
const isPro = computeIsPro(user)
const isAdmin = Boolean(user?.is_admin)
const planLimits = PLAN_LIMITS[isPro ? 'pro' : 'freemium']
return (
<AuthContext.Provider value={{
user,
loading,
login,
register,
logout,
loginWithToken,
loadUser,
isPro,
isAdmin,
planLimits,
}}>
{children}
</AuthContext.Provider>
)
}
export const useAuth = () => useContext(AuthContext)