import {
configureStore,
createSlice,
PayloadAction,
createAsyncThunk,
combineReducers
} from '@reduxjs/toolkit';
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
// ============================================
// Types
// ============================================
interface User {
id: string;
name: string;
email: string;
}
interface AuthState {
user: User | null;
token: string | null;
status: 'idle' | 'loading' | 'succeeded' | 'failed';
error: string | null;
}
interface CounterState {
value: number;
}
// ============================================
// Async Thunks
// ============================================
export const loginUser = createAsyncThunk(
'auth/login',
async (credentials: { email: string }, { rejectWithValue }) => {
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
if (credentials.email === 'error@example.com') throw new Error('Login failed');
return {
user: { id: '1', name: 'John Doe', email: credentials.email },
token: 'fake-jwt-token'
};
} catch (error: any) {
return rejectWithValue(error.message);
}
}
);
// ============================================
// Slices
// ============================================
const authSlice = createSlice({
name: 'auth',
initialState: {
user: null,
token: null,
status: 'idle',
error: null,
} as AuthState,
reducers: {
logout: (state) => {
state.user = null;
state.token = null;
state.status = 'idle';
},
},
extraReducers: (builder) => {
builder
.addCase(loginUser.pending, (state) => {
state.status = 'loading';
state.error = null;
})
.addCase(loginUser.fulfilled, (state, action) => {
state.status = 'succeeded';
state.user = action.payload.user;
state.token = action.payload.token;
})
.addCase(loginUser.rejected, (state, action) => {
state.status = 'failed';
state.error = action.payload as string;
});
},
});
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 } as CounterState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload;
},
},
});
// ============================================
// Store Configuration
// ============================================
const rootReducer = combineReducers({
auth: authSlice.reducer,
counter: counterSlice.reducer,
});
export const store = configureStore({
reducer: rootReducer,
devTools: process.env.NODE_ENV !== 'production',
});
// ============================================
// TypeScript Helpers
// ============================================
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// Use these typed hooks throughout the app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
// ============================================
// Exports
// ============================================
export const { logout } = authSlice.actions;
export const { increment, decrement, incrementByAmount } = counterSlice.actions;