Start integrating the onboarding flow with backend 1

This commit is contained in:
Urtzi Alfaro
2025-09-03 18:29:56 +02:00
parent a55d48e635
commit a11fdfba24
31 changed files with 1202 additions and 1142 deletions

View File

@@ -86,7 +86,13 @@ class AuthService {
// Authentication endpoints
async register(userData: UserRegistration): Promise<ApiResponse<TokenResponse>> {
return apiClient.post(`${this.baseUrl}/register`, userData);
const response = await apiClient.post<TokenResponse>(`${this.baseUrl}/register`, userData);
if (response.success && response.data) {
this.handleSuccessfulAuth(response.data);
}
return response;
}
async login(credentials: UserLogin): Promise<ApiResponse<TokenResponse>> {
@@ -164,54 +170,70 @@ class AuthService {
return apiClient.post(`${this.baseUrl}/verify-email/confirm`, { token });
}
// Local auth state management
// Local auth state management - Now handled by Zustand store
private handleSuccessfulAuth(tokenData: TokenResponse) {
localStorage.setItem('access_token', tokenData.access_token);
if (tokenData.refresh_token) {
localStorage.setItem('refresh_token', tokenData.refresh_token);
}
if (tokenData.user) {
localStorage.setItem('user_data', JSON.stringify(tokenData.user));
if (tokenData.user.tenant_id) {
localStorage.setItem('tenant_id', tokenData.user.tenant_id);
apiClient.setTenantId(tokenData.user.tenant_id);
}
}
// Set auth token for API client
apiClient.setAuthToken(tokenData.access_token);
// Set tenant ID for API client if available
if (tokenData.user?.tenant_id) {
apiClient.setTenantId(tokenData.user.tenant_id);
}
}
private clearAuthData() {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
localStorage.removeItem('user_data');
localStorage.removeItem('tenant_id');
// Clear API client tokens
apiClient.removeAuthToken();
}
// Utility methods
// Utility methods - Now get data from Zustand store
isAuthenticated(): boolean {
return !!localStorage.getItem('access_token');
const authStorage = localStorage.getItem('auth-storage');
if (!authStorage) return false;
try {
const { state } = JSON.parse(authStorage);
return state?.isAuthenticated || false;
} catch {
return false;
}
}
getCurrentUserData(): UserData | null {
const userData = localStorage.getItem('user_data');
return userData ? JSON.parse(userData) : null;
const authStorage = localStorage.getItem('auth-storage');
if (!authStorage) return null;
try {
const { state } = JSON.parse(authStorage);
return state?.user || null;
} catch {
return null;
}
}
getAccessToken(): string | null {
return localStorage.getItem('access_token');
const authStorage = localStorage.getItem('auth-storage');
if (!authStorage) return null;
try {
const { state } = JSON.parse(authStorage);
return state?.token || null;
} catch {
return null;
}
}
getRefreshToken(): string | null {
return localStorage.getItem('refresh_token');
const authStorage = localStorage.getItem('auth-storage');
if (!authStorage) return null;
try {
const { state } = JSON.parse(authStorage);
return state?.refreshToken || null;
} catch {
return null;
}
}
getTenantId(): string | null {
return localStorage.getItem('tenant_id');
const userData = this.getCurrentUserData();
return userData?.tenant_id || null;
}
// Check if token is expired (basic check)

View File

@@ -1,5 +1,32 @@
import axios, { AxiosInstance, AxiosResponse, AxiosError, InternalAxiosRequestConfig } from 'axios';
// Utility functions to access auth and tenant store data from localStorage
const getAuthData = () => {
const authStorage = localStorage.getItem('auth-storage');
if (!authStorage) return null;
try {
const { state } = JSON.parse(authStorage);
return state;
} catch {
return null;
}
};
const getTenantData = () => {
const tenantStorage = localStorage.getItem('tenant-storage');
if (!tenantStorage) return null;
try {
const { state } = JSON.parse(tenantStorage);
return state;
} catch {
return null;
}
};
const clearAuthData = () => {
localStorage.removeItem('auth-storage');
};
export interface ApiResponse<T = any> {
data: T;
success: boolean;
@@ -41,12 +68,15 @@ class ApiClient {
// Request interceptor - add auth token and tenant ID
this.axiosInstance.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const token = localStorage.getItem('access_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
const authData = getAuthData();
if (authData?.token) {
config.headers.Authorization = `Bearer ${authData.token}`;
}
const tenantId = localStorage.getItem('tenant_id');
// Get tenant ID from tenant store (priority) or fallback to user's tenant_id
const tenantData = getTenantData();
const tenantId = tenantData?.currentTenant?.id || authData?.user?.tenant_id;
if (tenantId) {
config.headers['X-Tenant-ID'] = tenantId;
}
@@ -67,8 +97,8 @@ class ApiClient {
originalRequest._retry = true;
try {
const refreshToken = localStorage.getItem('refresh_token');
if (refreshToken) {
const authData = getAuthData();
if (authData?.refreshToken) {
const newToken = await this.refreshToken();
originalRequest.headers.Authorization = `Bearer ${newToken}`;
return this.axiosInstance(originalRequest);
@@ -113,29 +143,34 @@ class ApiClient {
}
private async performTokenRefresh(): Promise<string> {
const refreshToken = localStorage.getItem('refresh_token');
if (!refreshToken) {
const authData = getAuthData();
if (!authData?.refreshToken) {
throw new Error('No refresh token available');
}
const response = await axios.post(`${API_BASE_URL}/auth/refresh`, {
refresh_token: refreshToken,
refresh_token: authData.refreshToken,
});
const { access_token, refresh_token } = response.data;
localStorage.setItem('access_token', access_token);
if (refresh_token) {
localStorage.setItem('refresh_token', refresh_token);
}
// Update the Zustand store by modifying the auth-storage directly
const newAuthData = {
...authData,
token: access_token,
refreshToken: refresh_token || authData.refreshToken
};
localStorage.setItem('auth-storage', JSON.stringify({
state: newAuthData,
version: 0
}));
return access_token;
}
private handleAuthFailure() {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
localStorage.removeItem('user_data');
localStorage.removeItem('tenant_id');
clearAuthData();
// Redirect to login
window.location.href = '/login';
@@ -197,19 +232,16 @@ class ApiClient {
};
}
// Utility methods
// Utility methods - Now work with Zustand store
setAuthToken(token: string) {
localStorage.setItem('access_token', token);
this.axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${token}`;
}
removeAuthToken() {
localStorage.removeItem('access_token');
delete this.axiosInstance.defaults.headers.common['Authorization'];
}
setTenantId(tenantId: string) {
localStorage.setItem('tenant_id', tenantId);
this.axiosInstance.defaults.headers.common['X-Tenant-ID'] = tenantId;
}

View File

@@ -1,6 +1,6 @@
import { apiClient, ApiResponse } from './client';
// Request/Response Types based on backend schemas
// Request/Response Types based on backend schemas - UPDATED TO MATCH BACKEND
export interface BakeryRegistration {
name: string;
address: string;
@@ -101,7 +101,7 @@ class TenantService {
// Tenant CRUD operations
async createTenant(tenantData: BakeryRegistration): Promise<ApiResponse<TenantResponse>> {
return apiClient.post(`${this.baseUrl}`, tenantData);
return apiClient.post(`${this.baseUrl}/register`, tenantData);
}
async getTenant(tenantId: string): Promise<ApiResponse<TenantResponse>> {
@@ -151,15 +151,31 @@ class TenantService {
}
async switchTenant(tenantId: string): Promise<ApiResponse<{ message: string; tenant: TenantResponse }>> {
const response = await apiClient.post(`${this.baseUrl}/${tenantId}/switch`);
if (response.success && response.data?.tenant) {
// Update local tenant context
localStorage.setItem('tenant_id', tenantId);
apiClient.setTenantId(tenantId);
// Frontend-only tenant switching since backend doesn't have this endpoint
// We'll simulate the response and update the tenant store directly
try {
const tenant = await this.getTenant(tenantId);
if (tenant.success) {
// Update API client tenant context
apiClient.setTenantId(tenantId);
return {
success: true,
data: {
message: 'Tenant switched successfully',
tenant: tenant.data
}
};
} else {
throw new Error('Tenant not found');
}
} catch (error) {
return {
success: false,
data: null,
error: error instanceof Error ? error.message : 'Failed to switch tenant'
};
}
return response;
}
// Member management
@@ -228,33 +244,33 @@ class TenantService {
}
// Utility methods
async getUserTenants(): Promise<ApiResponse<TenantResponse[]>> {
return apiClient.get(`${this.baseUrl}/my-tenants`);
async getUserTenants(userId?: string): Promise<ApiResponse<TenantResponse[]>> {
// If no userId provided, we'll get it from the auth store/token
return apiClient.get(`${this.baseUrl}/users/${userId || 'current'}`);
}
async validateTenantSlug(slug: string): Promise<ApiResponse<{ available: boolean; suggestions?: string[] }>> {
return apiClient.get(`${this.baseUrl}/validate-slug/${slug}`);
}
// Local state management helpers
// Local state management helpers - Now uses tenant store
getCurrentTenantId(): string | null {
return localStorage.getItem('tenant_id');
// This will be handled by the tenant store
return null;
}
getCurrentTenantData(): TenantResponse | null {
const tenantData = localStorage.getItem('tenant_data');
return tenantData ? JSON.parse(tenantData) : null;
// This will be handled by the tenant store
return null;
}
setCurrentTenant(tenant: TenantResponse) {
localStorage.setItem('tenant_id', tenant.id);
localStorage.setItem('tenant_data', JSON.stringify(tenant));
// This will be handled by the tenant store
apiClient.setTenantId(tenant.id);
}
clearCurrentTenant() {
localStorage.removeItem('tenant_id');
localStorage.removeItem('tenant_data');
// This will be handled by the tenant store
}
// Business type helpers