Skip to content

Commit

Permalink
Refactor folder structure (#99)
Browse files Browse the repository at this point in the history
* Move add edit note to feature folder

* Moved MealsList to widgets

* Moved notes helpers to entities

* Moved DeleteNote to features

* Refactored module public api exports

* Moved CategorySelect and useCategorySelect to entities

* Moved note form data type to features

* Moved useAppSelector and useAppDispatch to store.ts

* Removed usePopover

* Moved utils to shared/lib

* Moved types to shared/types

* Moved hooks to shared/hooks

* Moved config to shared/config

* Moved components to shared/components

* Moved api to shared/api

* Reorganized app folder

* Fixed app folder case sensitivity issue

* Removed default exports

* Moved src/testing to tests

* Moved src/test-utils to tests

* Moved setupTests.ts to tests

* Renamed AutocompleteOptionType to AutocompleteOption
  • Loading branch information
pkirilin authored May 7, 2024
1 parent f47fbe9 commit a7c6d78
Show file tree
Hide file tree
Showing 210 changed files with 702 additions and 900 deletions.
2 changes: 1 addition & 1 deletion src/frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
<script type="module" src="/src/app/index.tsx"></script>
</body>

</html>
10 changes: 0 additions & 10 deletions src/frontend/src/App.tsx

This file was deleted.

28 changes: 0 additions & 28 deletions src/frontend/src/AppProvider.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { AUTH_CHECK_INTERVAL } from './config';
import { renderApp } from './testing/render';
import { AUTH_CHECK_INTERVAL } from '@/shared/config';
import { renderApp } from '@tests/render';

test('user can login and logout', async () => {
renderApp();
Expand Down
8 changes: 8 additions & 0 deletions src/frontend/src/app/Root/Root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { type PropsWithChildren, type FC } from 'react';
import { RouterProvider } from 'react-router-dom';
import { createAppRouter } from '../router';
import { RootLoader } from './RootLoader';

export const Root: FC<PropsWithChildren> = ({ children }) => (
<RouterProvider router={createAppRouter(children)} fallbackElement={<RootLoader />} />
);
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { type FC } from 'react';
import { AppName } from '@/shared/ui';
import { CenteredLayout } from '@/widgets/layout';

export const AppLoader: FC = () => (
export const RootLoader: FC = () => (
<Grow in>
<Box>
<CenteredLayout>
Expand Down
1 change: 1 addition & 0 deletions src/frontend/src/app/Root/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Root';
24 changes: 24 additions & 0 deletions src/frontend/src/app/RootProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { CssBaseline, StyledEngineProvider, ThemeProvider } from '@mui/material';
import { LocalizationProvider } from '@mui/x-date-pickers';
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
import { type PropsWithChildren, type FC } from 'react';
import { Provider } from 'react-redux';
import { type Store } from 'redux';
import { theme } from './theme';

interface Props {
store: Store;
}

export const RootProvider: FC<PropsWithChildren<Props>> = ({ children, store }) => (
<StyledEngineProvider injectFirst>
<ThemeProvider theme={theme}>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<Provider store={store}>
<CssBaseline />
{children}
</Provider>
</LocalizationProvider>
</ThemeProvider>
</StyledEngineProvider>
);
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ReactGA from 'react-ga4';
import { GOOGLE_ANALYTICS_MEASUREMENT_ID } from './config';
import { GOOGLE_ANALYTICS_MEASUREMENT_ID } from '@/shared/config';

export const initGoogleAnalytics = (): void => {
ReactGA.initialize(GOOGLE_ANALYTICS_MEASUREMENT_ID);
Expand Down
14 changes: 7 additions & 7 deletions src/frontend/src/index.tsx → src/frontend/src/app/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import 'date-fns';
import { initBrowserMockApi } from 'tests/mockApi';
import { createRoot } from 'react-dom/client';
import App from './App';
import AppProvider from './AppProvider';
import { GOOGLE_ANALYTICS_ENABLED, MSW_ENABLED } from './config';
import { GOOGLE_ANALYTICS_ENABLED, MSW_ENABLED } from '@/shared/config';
import { initGoogleAnalytics } from './googleAnalytics';
import store from './store';
import { Root } from './Root';
import { RootProvider } from './RootProvider';
import { store } from './store';

void (async () => {
if (MSW_ENABLED) {
Expand All @@ -25,8 +25,8 @@ void (async () => {
const root = createRoot(container);

root.render(
<AppProvider store={store}>
<App />
</AppProvider>,
<RootProvider store={store}>
<Root />
</RootProvider>,
);
})();
15 changes: 7 additions & 8 deletions src/frontend/src/store.ts → src/frontend/src/app/store.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { configureStore } from '@reduxjs/toolkit';
import { api } from './api';
import { useAppDispatch, useAppSelector } from './features/__shared__/hooks';
import pagesReducer from './features/pages/slice';
import productsReducer from './features/products/store';
import { type TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import pagesReducer from '../features/pages/slice';
import productsReducer from '../features/products/store';
import { api } from '../shared/api';

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export const configureAppStore = () =>
Expand All @@ -16,12 +16,11 @@ export const configureAppStore = () =>
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(api.middleware),
});

const store: ReturnType<typeof configureAppStore> = configureAppStore();
export const store: ReturnType<typeof configureAppStore> = configureAppStore();

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export type AppStore = ReturnType<typeof configureAppStore>;

export { useAppSelector, useAppDispatch };

export default store;
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ declare module '@mui/material/styles' {
interface ThemeOptions {}
}

export default createTheme({
export const theme = createTheme({
palette: {
mode: 'light',
primary: {
Expand Down
3 changes: 0 additions & 3 deletions src/frontend/src/components/AppFab/index.ts

This file was deleted.

3 changes: 0 additions & 3 deletions src/frontend/src/components/AppSelect/index.ts

This file was deleted.

5 changes: 0 additions & 5 deletions src/frontend/src/components/DatePicker/index.ts

This file was deleted.

6 changes: 0 additions & 6 deletions src/frontend/src/components/index.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { api } from 'src/api';
import { type SelectOption } from 'src/types';
import { type Category } from '../types';
import { api } from '@/shared/api';
import { type SelectOption } from '@/shared/types';
import { type Category } from '../model';
import {
type CreateCategoryRequest,
type DeleteCategoryRequest,
type EditCategoryRequest,
} from './contracts';

export const categoriesApi = api.injectEndpoints({
export const categoryApi = api.injectEndpoints({
endpoints: builder => ({
getCategories: builder.query<Category[], unknown>({
query: () => '/api/v1/categories',
providesTags: ['category'],
}),

getCategorySelectOptions: builder.query<SelectOption[], unknown>({
getCategorySelectOptions: builder.query<SelectOption[], void>({
query: () => '/api/v1/categories/autocomplete',
providesTags: ['category'],
}),
Expand Down
2 changes: 2 additions & 0 deletions src/frontend/src/entities/category/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './categoryApi';
export * from './contracts';
4 changes: 4 additions & 0 deletions src/frontend/src/entities/category/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './api';
export * as categoryLib from './lib';
export * as categoryModel from './model';
export * from './ui';
1 change: 1 addition & 0 deletions src/frontend/src/entities/category/lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useCategorySelectData';
16 changes: 16 additions & 0 deletions src/frontend/src/entities/category/lib/useCategorySelectData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { type SelectOption } from '@/shared/types';
import { categoryApi } from '../api';

export interface CategorySelectData {
data: SelectOption[];
isLoading: boolean;
}

export const useCategorySelectData = (): CategorySelectData => {
const query = categoryApi.useGetCategorySelectOptionsQuery();

return {
data: query.data ?? [],
isLoading: query.isLoading,
};
};
5 changes: 5 additions & 0 deletions src/frontend/src/entities/category/model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface Category {
id: number;
name: string;
countProducts: number;
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { type FC } from 'react';
import { AppSelect } from 'src/components';
import { type SelectOption, type SelectProps } from 'src/types';
import { type SelectOption, type SelectProps } from '@/shared/types';
import { AppSelect } from '@/shared/ui';

interface CategorySelectProps extends SelectProps<SelectOption> {
options: SelectOption[];
optionsLoading: boolean;
}

const CategorySelect: FC<CategorySelectProps> = ({
export const CategorySelect: FC<CategorySelectProps> = ({
label,
placeholder,
value = null,
Expand All @@ -34,5 +34,3 @@ const CategorySelect: FC<CategorySelectProps> = ({
/>
);
};

export default CategorySelect;
1 change: 1 addition & 0 deletions src/frontend/src/entities/category/ui/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './CategorySelect';
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type MealType } from '../models';
import { type MealType } from '../model';

export interface GetNotesRequest {
pageId: number;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './contracts';
export * from './notesApi';
export * from './noteApi';
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { api } from 'src/api';
import { createUrl } from 'src/utils';
import { type NoteItem } from '../models';
import { type EditNoteRequest, type CreateNoteRequest, type GetNotesRequest } from './contracts';
import {
type EditNoteRequest,
type CreateNoteRequest,
type GetNotesRequest,
type noteModel,
} from '@/entities/note';
import { api } from '@/shared/api';
import { createUrl } from '@/shared/lib';

export const notesApi = api.injectEndpoints({
export const noteApi = api.injectEndpoints({
endpoints: builder => ({
getNotes: builder.query<NoteItem[], GetNotesRequest>({
getNotes: builder.query<noteModel.NoteItem[], GetNotesRequest>({
query: request => createUrl('/api/v1/notes', { ...request }),
providesTags: ['note'],
}),
Expand Down
3 changes: 3 additions & 0 deletions src/frontend/src/entities/note/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './api';
export * as noteModel from './model';
export * as noteLib from './lib';
19 changes: 19 additions & 0 deletions src/frontend/src/entities/note/lib/groupByMealType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { type NoteItem, type MealType } from '../model';

const assignNoteToGroup = (
groups: Map<MealType, NoteItem[]>,
note: NoteItem,
): Map<MealType, NoteItem[]> => {
const notes = groups.get(note.mealType);

if (notes) {
notes.push(note);
} else {
groups.set(note.mealType, [note]);
}

return groups;
};

export const groupByMealType = (notes: NoteItem[]): Map<MealType, NoteItem[]> =>
notes.reduce(assignNoteToGroup, new Map<MealType, NoteItem[]>());
3 changes: 3 additions & 0 deletions src/frontend/src/entities/note/lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './useNotes';
export * from './groupByMealType';
export * from './mealsHelpers';
21 changes: 21 additions & 0 deletions src/frontend/src/entities/note/lib/mealsHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { MealType } from '../model';

const AVAILABLE_MEALS: Map<MealType, string> = new Map<MealType, string>([
[MealType.Breakfast, 'Breakfast'],
[MealType.SecondBreakfast, 'Second breakfast'],
[MealType.Lunch, 'Lunch'],
[MealType.AfternoonSnack, 'Afternoon snack'],
[MealType.Dinner, 'Dinner'],
]);

export const getMealTypes = (): MealType[] => Array.from(AVAILABLE_MEALS.keys());

export const getMealName = (mealType: MealType): string => {
const mealName = AVAILABLE_MEALS.get(mealType);

if (!mealName) {
throw new Error(`Meal type = '${mealType}' doesn't exist`);
}

return mealName;
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { notesApi } from '../api';
import { type NoteItem } from '../models';
import { noteApi } from '../api/noteApi';
import { type NoteItem } from '../model';

interface Result {
data: NoteItem[];
Expand All @@ -8,7 +8,7 @@ interface Result {
}

export const useNotes = (pageId: number): Result => {
return notesApi.useGetNotesQuery(
return noteApi.useGetNotesQuery(
{
pageId,
},
Expand Down
18 changes: 18 additions & 0 deletions src/frontend/src/entities/note/model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export enum MealType {
Breakfast = 1,
SecondBreakfast = 2,
Lunch = 3,
AfternoonSnack = 4,
Dinner = 5,
}

export interface NoteItem {
id: number;
mealType: MealType;
displayOrder: number;
productId: number;
productName: string;
productQuantity: number;
productDefaultQuantity: number;
calories: number;
}
Loading

0 comments on commit a7c6d78

Please sign in to comment.