feat: initial commit — HermesMessages SaaS platform
Backend (FastAPI + Python 3.12): - Multi-tenant auth with JWT: login, register, refresh, Meta OAuth - Business & BusinessConfig management - WhatsApp webhook with HMAC signature verification - Bot engine powered by Claude AI - Calendar availability with Redis caching - Reservations CRUD with status management - Dashboard analytics (stats, agenda, peak hours) - Billing & plan management - Admin panel with platform-wide stats - Async bcrypt via asyncio.to_thread - IntegrityError handling for concurrent registration race conditions Frontend (React 18 + Vite + Tailwind CSS): - Multi-step guided registration form with helper text on every field - Login page with show/hide password toggle - Protected routes with AuthContext - Dashboard with stats cards, bar chart, and daily agenda - Reservations list with search, filters, and inline status actions - Calendar with weekly view, slot availability, and date blocking - Config page: business info, schedules, bot personality - Billing page with plan comparison and usage bar Design system: - Bricolage Grotesque + DM Sans typography - Emerald primary palette with semantic color tokens - scale(0.97) button press feedback, ease-out animations - Skeleton loaders, stagger animations, prefers-reduced-motion support - Accessible: aria-labels, visible focus rings, 4.5:1 contrast Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
73
frontend/src/lib/api.js
Normal file
73
frontend/src/lib/api.js
Normal file
@ -0,0 +1,73 @@
|
||||
import axios from 'axios'
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: '/api',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
|
||||
api.interceptors.request.use((config) => {
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) config.headers.Authorization = `Bearer ${token}`
|
||||
return config
|
||||
})
|
||||
|
||||
api.interceptors.response.use(
|
||||
(res) => res,
|
||||
(err) => {
|
||||
if (err.response?.status === 401) {
|
||||
localStorage.removeItem('token')
|
||||
window.location.href = '/login'
|
||||
}
|
||||
return Promise.reject(err)
|
||||
}
|
||||
)
|
||||
|
||||
export const authApi = {
|
||||
register: (data) => api.post('/auth/register', data),
|
||||
login: (data) => api.post('/auth/login', data),
|
||||
logout: () => api.post('/auth/logout'),
|
||||
}
|
||||
|
||||
export const businessApi = {
|
||||
getMe: () => api.get('/business/me'),
|
||||
updateMe: (data) => api.put('/business/me', data),
|
||||
getConfig: () => api.get('/business/me/config'),
|
||||
updateConfig: (data) => api.put('/business/me/config', data),
|
||||
}
|
||||
|
||||
export const whatsappApi = {
|
||||
getStatus: () => api.get('/whatsapp/status'),
|
||||
connect: (data) => api.post('/whatsapp/connect', data),
|
||||
disconnect: () => api.post('/whatsapp/disconnect'),
|
||||
}
|
||||
|
||||
export const reservationsApi = {
|
||||
list: (params) => api.get('/reservations/', { params }),
|
||||
create: (data) => api.post('/reservations/', data),
|
||||
get: (id) => api.get(`/reservations/${id}`),
|
||||
update: (id, data) => api.put(`/reservations/${id}`, data),
|
||||
updateStatus: (id, status) => api.patch(`/reservations/${id}/status`, { status }),
|
||||
delete: (id) => api.delete(`/reservations/${id}`),
|
||||
}
|
||||
|
||||
export const calendarApi = {
|
||||
getAvailability: (date) => api.get('/calendar/availability', { params: { date } }),
|
||||
getAvailabilityRange: (start, end) =>
|
||||
api.get('/calendar/availability/range', { params: { start_date: start, end_date: end } }),
|
||||
blockDate: (date) => api.post('/calendar/blocked-dates', { date }),
|
||||
unblockDate: (date) => api.delete(`/calendar/blocked-dates/${date}`),
|
||||
}
|
||||
|
||||
export const dashboardApi = {
|
||||
getStats: () => api.get('/dashboard/stats'),
|
||||
getAgenda: (date) => api.get('/dashboard/agenda', { params: { date } }),
|
||||
getPeakHours: () => api.get('/dashboard/peak-hours'),
|
||||
}
|
||||
|
||||
export const billingApi = {
|
||||
getPlan: () => api.get('/billing/plan'),
|
||||
getUsage: () => api.get('/billing/usage'),
|
||||
upgrade: (plan) => api.post('/billing/upgrade', { plan }),
|
||||
}
|
||||
|
||||
export default api
|
||||
Reference in New Issue
Block a user