2025-11-10 12:30:08 +01:00

90 lines
2.2 KiB
TypeScript

import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit";
export interface SidebarItem {
id?: string;
title: string;
path: string;
icon?: string; // icon name from backend; map client-side if needed
permissions?: string[]; // required permissions for visibility
children?: SidebarItem[];
}
export interface AppMetadata {
groups?: string[];
job_titles?: string[];
merchants?: string[];
message?: string;
sidebar?: SidebarItem[];
success: boolean;
}
interface MetadataState {
data: AppMetadata | null;
status: "idle" | "loading" | "succeeded" | "failed";
error: string | null;
lastFetchedAt: number | null;
}
const initialState: MetadataState = {
data: null,
status: "idle",
error: null,
lastFetchedAt: null,
};
export const fetchMetadata = createAsyncThunk<
AppMetadata,
void,
{ rejectValue: string }
>("metadata/fetch", async (_, { rejectWithValue }) => {
try {
const res = await fetch("/api/metadata", {
method: "GET",
cache: "no-store",
});
const data = await res.json();
if (!res.ok) {
return rejectWithValue(data?.message || "Failed to fetch metadata");
}
return data as AppMetadata;
} catch (err) {
return rejectWithValue((err as Error)?.message || "Network error");
}
});
const metadataSlice = createSlice({
name: "metadata",
initialState,
reducers: {
clearMetadata(state) {
state.data = null;
state.error = null;
state.status = "idle";
state.lastFetchedAt = null;
},
},
extraReducers: builder => {
builder
.addCase(fetchMetadata.pending, state => {
state.status = "loading";
state.error = null;
})
.addCase(
fetchMetadata.fulfilled,
(state, action: PayloadAction<AppMetadata>) => {
state.status = "succeeded";
state.data = action.payload;
state.lastFetchedAt = Date.now();
}
)
.addCase(fetchMetadata.rejected, (state, action) => {
state.status = "failed";
state.error = (action.payload as string) || "Unknown error";
});
},
});
export const { clearMetadata } = metadataSlice.actions;
export default metadataSlice.reducer;