188 lines
6.2 KiB
TypeScript
188 lines
6.2 KiB
TypeScript
import { createSlice, createAsyncThunk, type PayloadAction } from '@reduxjs/toolkit';
|
|
import { themeService } from '@/services/theme-service';
|
|
import { extractDomainFromUrl, applyThemeColors } from '@/utils/theme';
|
|
|
|
export interface ThemeData {
|
|
id: string;
|
|
name: string;
|
|
logo_file_path: string | null;
|
|
favicon_file_path: string | null;
|
|
primary_color: string | null;
|
|
secondary_color: string | null;
|
|
accent_color: string | null;
|
|
}
|
|
|
|
interface ThemeState {
|
|
theme: ThemeData | null;
|
|
logoUrl: string | null;
|
|
faviconUrl: string | null;
|
|
isLoading: boolean;
|
|
error: string | null;
|
|
isInitialized: boolean;
|
|
}
|
|
|
|
// Default theme (super admin theme)
|
|
const defaultTheme: ThemeData = {
|
|
id: '',
|
|
name: 'Default',
|
|
logo_file_path: null,
|
|
favicon_file_path: null,
|
|
primary_color: '#112868',
|
|
secondary_color: '#23DCE1',
|
|
accent_color: '#084CC8',
|
|
};
|
|
|
|
const initialState: ThemeState = {
|
|
theme: null,
|
|
logoUrl: null,
|
|
faviconUrl: null,
|
|
isLoading: false,
|
|
error: null,
|
|
isInitialized: false,
|
|
};
|
|
|
|
// Async thunk to fetch theme
|
|
export const fetchThemeAsync = createAsyncThunk<
|
|
ThemeData,
|
|
string | undefined,
|
|
{ rejectValue: { message: string } }
|
|
>('theme/fetch', async (domain, { rejectWithValue }) => {
|
|
try {
|
|
const domainToUse = domain || extractDomainFromUrl();
|
|
if (!domainToUse) {
|
|
return rejectWithValue({ message: 'Domain not found' });
|
|
}
|
|
|
|
const response = await themeService.getTheme(domainToUse);
|
|
if (response.success && response.data) {
|
|
return response.data;
|
|
}
|
|
return rejectWithValue({ message: 'Failed to fetch theme' });
|
|
} catch (error: any) {
|
|
return rejectWithValue({
|
|
message: error?.response?.data?.error?.message || error?.message || 'Failed to fetch theme',
|
|
});
|
|
}
|
|
});
|
|
|
|
// Async thunk to fetch authenticated theme
|
|
export const fetchAuthThemeAsync = createAsyncThunk<
|
|
ThemeData,
|
|
void,
|
|
{ rejectValue: { message: string } }
|
|
>('theme/fetchAuth', async (_, { rejectWithValue }) => {
|
|
try {
|
|
const response = await themeService.getAuthTenantTheme();
|
|
if (response.success && response.data) {
|
|
return response.data;
|
|
}
|
|
return rejectWithValue({ message: 'Failed to fetch authenticated theme' });
|
|
} catch (error: any) {
|
|
return rejectWithValue({
|
|
message: error?.response?.data?.error?.message || error?.message || 'Failed to fetch authenticated theme',
|
|
});
|
|
}
|
|
});
|
|
|
|
const themeSlice = createSlice({
|
|
name: 'theme',
|
|
initialState,
|
|
reducers: {
|
|
setDefaultTheme: (state) => {
|
|
state.theme = defaultTheme;
|
|
state.logoUrl = null;
|
|
state.faviconUrl = null;
|
|
applyThemeColors(defaultTheme.primary_color, defaultTheme.secondary_color, defaultTheme.accent_color);
|
|
},
|
|
clearTheme: (state) => {
|
|
state.theme = null;
|
|
state.logoUrl = null;
|
|
state.faviconUrl = null;
|
|
state.error = null;
|
|
applyThemeColors(defaultTheme.primary_color, defaultTheme.secondary_color, defaultTheme.accent_color);
|
|
},
|
|
updateTheme: (state, action: PayloadAction<Partial<ThemeData>>) => {
|
|
if (state.theme) {
|
|
state.theme = { ...state.theme, ...action.payload };
|
|
if (action.payload.logo_file_path !== undefined) {
|
|
state.logoUrl = action.payload.logo_file_path;
|
|
}
|
|
if (action.payload.favicon_file_path !== undefined) {
|
|
state.faviconUrl = action.payload.favicon_file_path;
|
|
}
|
|
const primaryColor = action.payload.primary_color ?? state.theme.primary_color;
|
|
const secondaryColor = action.payload.secondary_color ?? state.theme.secondary_color;
|
|
const accentColor = action.payload.accent_color ?? state.theme.accent_color;
|
|
if (primaryColor || secondaryColor || accentColor) {
|
|
applyThemeColors(primaryColor, secondaryColor, accentColor);
|
|
}
|
|
}
|
|
},
|
|
},
|
|
extraReducers: (builder) => {
|
|
builder
|
|
.addCase(fetchThemeAsync.pending, (state) => {
|
|
state.isLoading = true;
|
|
state.error = null;
|
|
})
|
|
.addCase(fetchThemeAsync.fulfilled, (state, action: PayloadAction<ThemeData>) => {
|
|
state.isLoading = false;
|
|
state.theme = action.payload;
|
|
// Use URLs directly from API response (logo_file_path and favicon_file_path contain full URLs)
|
|
state.logoUrl = action.payload.logo_file_path;
|
|
state.faviconUrl = action.payload.favicon_file_path;
|
|
state.error = null;
|
|
state.isInitialized = true;
|
|
|
|
// Apply theme colors
|
|
applyThemeColors(
|
|
action.payload.primary_color,
|
|
action.payload.secondary_color,
|
|
action.payload.accent_color
|
|
);
|
|
})
|
|
.addCase(fetchThemeAsync.rejected, (state, action) => {
|
|
state.isLoading = false;
|
|
state.error = action.payload?.message || 'Failed to fetch theme';
|
|
// Use default theme on error
|
|
state.theme = defaultTheme;
|
|
state.logoUrl = null;
|
|
state.faviconUrl = null;
|
|
state.isInitialized = true;
|
|
applyThemeColors(defaultTheme.primary_color, defaultTheme.secondary_color, defaultTheme.accent_color);
|
|
})
|
|
.addCase(fetchAuthThemeAsync.pending, (state) => {
|
|
state.isLoading = true;
|
|
state.error = null;
|
|
})
|
|
.addCase(fetchAuthThemeAsync.fulfilled, (state, action: PayloadAction<ThemeData>) => {
|
|
state.isLoading = false;
|
|
state.theme = action.payload;
|
|
state.logoUrl = action.payload.logo_file_path;
|
|
state.faviconUrl = action.payload.favicon_file_path;
|
|
state.error = null;
|
|
state.isInitialized = true;
|
|
|
|
applyThemeColors(
|
|
action.payload.primary_color,
|
|
action.payload.secondary_color,
|
|
action.payload.accent_color
|
|
);
|
|
})
|
|
.addCase(fetchAuthThemeAsync.rejected, (state, action) => {
|
|
state.isLoading = false;
|
|
state.error = action.payload?.message || 'Failed to fetch authenticated theme';
|
|
if (!state.theme) state.theme = defaultTheme;
|
|
state.isInitialized = true;
|
|
applyThemeColors(
|
|
state.theme.primary_color || defaultTheme.primary_color,
|
|
state.theme.secondary_color || defaultTheme.secondary_color,
|
|
state.theme.accent_color || defaultTheme.accent_color
|
|
);
|
|
});
|
|
},
|
|
});
|
|
|
|
export const { setDefaultTheme, clearTheme, updateTheme } = themeSlice.actions;
|
|
export default themeSlice.reducer;
|