ppt-tool/frontend/store/slices/authSlice.ts
Vadym Samoilenko 69a8829750 Phase 3: Bug fixes, feature enhancements, and polish
P0 Critical: presentation isolation (client scoping), storage super_admin fix,
template selection in worker, IMAGE_PROVIDERS list fix.

P1 High: template layout management UI (delete/filter/bulk), slide-based parsing
mode, LLM model listing & connection test, settings persistence to DB (Fernet
encryption), logout button.

P2 Polish: storage improvements (master deck files, per-client breakdown, bulk
delete, hard purge, client selector), image generation error visibility
(__image_error__ badge), hamster wheel loading animation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 12:58:52 +00:00

102 lines
2.4 KiB
TypeScript

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
export interface User {
id: string;
email: string;
displayName: string;
role: "super_admin" | "client_admin" | "user";
clientId?: string | null;
}
interface AuthState {
user: User | null;
isLoading: boolean;
isAuthenticated: boolean;
isDevMode: boolean;
}
const initialState: AuthState = {
user: null,
isLoading: true,
isAuthenticated: false,
isDevMode: false,
};
export const fetchCurrentUser = createAsyncThunk(
"auth/fetchCurrentUser",
async (_, { rejectWithValue }) => {
try {
const response = await fetch("/api/v1/auth/me");
if (response.status === 401) {
return rejectWithValue("Not authenticated");
}
if (!response.ok) {
return rejectWithValue("Failed to fetch user");
}
return await response.json();
} catch {
return rejectWithValue("Network error");
}
}
);
export const checkDevMode = createAsyncThunk(
"auth/checkDevMode",
async () => {
try {
const response = await fetch("/api/v1/auth/dev-status");
if (response.ok) {
const data = await response.json();
return data.dev_mode ?? false;
}
return false;
} catch {
return false;
}
}
);
export const logoutUser = createAsyncThunk(
"auth/logoutUser",
async () => {
await fetch("/api/v1/auth/logout", { method: "POST" });
return true;
}
);
const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
logout: (state) => {
state.user = null;
state.isAuthenticated = false;
},
},
extraReducers: (builder) => {
builder
.addCase(fetchCurrentUser.pending, (state) => {
state.isLoading = true;
})
.addCase(fetchCurrentUser.fulfilled, (state, action) => {
state.user = action.payload;
state.isAuthenticated = true;
state.isLoading = false;
})
.addCase(fetchCurrentUser.rejected, (state) => {
state.user = null;
state.isAuthenticated = false;
state.isLoading = false;
})
.addCase(checkDevMode.fulfilled, (state, action) => {
state.isDevMode = action.payload;
})
.addCase(logoutUser.fulfilled, (state) => {
state.user = null;
state.isAuthenticated = false;
});
},
});
export const { logout } = authSlice.actions;
export default authSlice.reducer;