Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,436 changes: 1,412 additions & 24 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "vitest",
"test:watch": "vitest watch",
"ionic:build": "npm run build",
"ionic:serve": "npm run start"
},
Expand Down Expand Up @@ -63,6 +65,7 @@
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "^6.12.0",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.44.0",
"eslint-config-prettier": "^9.0.0",
"eslint-config-standard-with-typescript": "^40.0.0",
Expand All @@ -74,6 +77,7 @@
"eslint-plugin-react": "^7.32.2",
"prettier": "^3.0.0",
"prettier-plugin-tailwindcss": "^0.4.0",
"typescript": "^5.1.6"
"typescript": "^5.1.6",
"vitest": "^3.1.4"
}
}
}
61 changes: 61 additions & 0 deletions redux/feature/auth/authSlice.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { configureStore } from '@reduxjs/toolkit';
import authReducer, { login, logout, setAuthError } from './authSlice';

describe('Auth Slice', () => {
const initialState = {
isAuthenticated: false,
user: null,
token: null,
error: null,
};

it('should return the initial state', () => {
const store = configureStore({
reducer: {
auth: authReducer,
},
});

expect(store.getState().auth).toEqual(initialState);
});

it('should handle login', () => {
const previousState = initialState;
const user = { id: '1', email: '[email protected]', name: 'Test User' };
const token = 'test-token';

const nextState = authReducer(
previousState,
login({ user, token })
);

expect(nextState.isAuthenticated).toBe(true);
expect(nextState.user).toEqual(user);
expect(nextState.token).toBe(token);
expect(nextState.error).toBeNull();
});

it('should handle logout', () => {
const loggedInState = {
isAuthenticated: true,
user: { id: '1', email: '[email protected]', name: 'Test User' },
token: 'test-token',
error: null,
};

const nextState = authReducer(loggedInState, logout());

expect(nextState).toEqual(initialState);
});

it('should handle setting auth error', () => {
const errorMessage = 'Authentication failed';

const nextState = authReducer(
initialState,
setAuthError(errorMessage)
);

expect(nextState.error).toBe(errorMessage);
});
});
51 changes: 51 additions & 0 deletions redux/feature/auth/authSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

// Define the shape of the authentication state
export interface AuthState {
isAuthenticated: boolean;
user: {
id?: string;
email?: string;
name?: string;
} | null;
token: string | null;
error: string | null;
}

// Initial state for authentication
const initialState: AuthState = {
isAuthenticated: false,
user: null,
token: null,
error: null,
};

const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
// Login action
login: (state, action: PayloadAction<{ user: AuthState['user'], token: string }>) => {
state.isAuthenticated = true;
state.user = action.payload.user;
state.token = action.payload.token;
state.error = null;
},

// Logout action
logout: (state) => {
state.isAuthenticated = false;
state.user = null;
state.token = null;
state.error = null;
},

// Set authentication error
setAuthError: (state, action: PayloadAction<string | null>) => {
state.error = action.payload;
},
},
});

export const { login, logout, setAuthError } = authSlice.actions;
export default authSlice.reducer;
14 changes: 8 additions & 6 deletions redux/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { useDispatch, useSelector } from "react-redux";
import type { TypedUseSelectorHook } from "react-redux";
import type { RootState, AppDispatch } from "./store";

// Source: https://redux-toolkit.js.org/tutorials/typescript (see "Define Typed Hooks" section)
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

// Auth-specific selectors
export const selectIsAuthenticated = (state: RootState) => state.auth.isAuthenticated;
export const selectCurrentUser = (state: RootState) => state.auth.user;
export const selectAuthError = (state: RootState) => state.auth.error;
81 changes: 12 additions & 69 deletions redux/store.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,17 @@
import { configureStore, combineReducers } from "@reduxjs/toolkit";
import themeSlice from "./feature/theme/themeSlice";
import salariesInputsReducer from "./feature/salariesInputs/salariesInputsSlice";
import searchJobsReducer from "@/redux/feature/searchJobs/searchJobs";
import createWebStorage from "redux-persist/lib/storage/createWebStorage";
import {
persistReducer,
persistStore,
FLUSH,
REHYDRATE,
PAUSE,
PERSIST,
PURGE,
REGISTER,
} from "redux-persist";

// NOTE - Source: https://mightycoders.xyz/redux-persist-failed-to-create-sync-storage-falling-back-to-noop-storage
// Regarding an error that occurs when using redux-persist with Next.js
// The error was 'redux-persist failed to create sync storage. falling back to noop storage.'
const createNoopStorage = () => {
return {
getItem(_key: any) {
return Promise.resolve(null);
},
setItem(_key: any, value: any) {
return Promise.resolve(value);
},
removeItem(_key: any) {
return Promise.resolve();
},
};
};

const storage =
typeof window !== "undefined"
? createWebStorage("local")
: createNoopStorage();

const persistConfig = {
key: "root",
storage,
version: 1,
// NOTE source 1: https://github.com/rt2zz/redux-persist#blacklist--whitelist
// NOTE source 2: Yilmaz's answer https://stackoverflow.com/questions/63761763/how-to-configure-redux-persist-with-redux-toolkit
whitelist: ["theme", "salariesInputs"],
};

const reducers = combineReducers({
theme: themeSlice,
salariesInputs: salariesInputsReducer,
searchJobs: searchJobsReducer,
// NOTE Add more reducers here if needed
});

const persistedReducer = persistReducer(persistConfig, reducers);
import { configureStore } from '@reduxjs/toolkit';
import searchJobsReducer from './feature/searchJobs/searchJobs';
import salariesInputsReducer from './feature/salariesInputs/salariesInputsSlice';
import themeReducer from './feature/theme/themeSlice';
import authReducer from './feature/auth/authSlice';

export const store = configureStore({
reducer: persistedReducer,
// NOTE - Source: https://redux-toolkit.js.org/usage/usage-guide#async-requests-with-createasyncthunk (See "Use with Redux-Persist" section)
// NOTE - Source: https://www.youtube.com/watch?v=b88Z5POQBwI
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
reducer: {
searchJobs: searchJobsReducer,
salariesInputs: salariesInputsReducer,
theme: themeReducer,
auth: authReducer,
},
});

// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch;

export const persistor = persistStore(store);
export type AppDispatch = typeof store.dispatch;
2 changes: 2 additions & 0 deletions setupTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Add any global test setup here if needed
import '@testing-library/jest-dom';
11 changes: 11 additions & 0 deletions vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';

export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./setupTests.ts'],
},
});