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>
111 lines
2.6 KiB
JavaScript
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)
|