Skip to content

Commit

Permalink
feat(favorites): add page to view favorite marked articles
Browse files Browse the repository at this point in the history
- add new routes `/favorite` and `/favorites`
- add `FavoritesPage`
- add i18n context
- update chapter-section-id
- update user state
  • Loading branch information
SimonGolms committed Jan 30, 2021
1 parent be00070 commit c82cdbe
Show file tree
Hide file tree
Showing 12 changed files with 184 additions and 34 deletions.
9 changes: 5 additions & 4 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@ import { Menu } from './components/Menu';
import { selectCurrentTheme } from './data/user/user.selector';
import { AboutPage } from './pages/About';
import { ChapterPage } from './pages/Chapter';
import { CreditsPage } from './pages/CreditsPage';
import { FavoritesPage } from './pages/Favorites';
import { HomePage } from './pages/Page';
import { SearchPage } from './pages/Search';
import { SettingsPage } from './pages/Settings';
// import Tutorial from './pages/Tutorial';
import { THEMES } from './utils/theme';
import { CreditsPage } from './pages/CreditsPage';

const App: React.FC = () => {
const currentTheme = useSelector(selectCurrentTheme);
Expand Down Expand Up @@ -66,9 +67,9 @@ const App: React.FC = () => {
<IonRouterOutlet id="main">
<Route exact path="/page/home" render={() => <HomePage />} />
<Route
path="/page/about"
render={() => <AboutPage />}
exact={true}
path={['/favorite', '/favorites']}
render={() => <FavoritesPage />}
exact
/>
<Route
path="/page/search"
Expand Down
3 changes: 2 additions & 1 deletion src/components/Chapters/ChapterFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { selectIsFavorite } from '../../data/user/user.selector';
import { addFavorite, removeFavorite } from '../../data/user/user.slice';
import {
ChapterId,
getIdFromChapterIdAndSectionId,
getChapterNextSectionUrl,
getChapterPreviousSectionUrl,
} from '../../utils/chapters';
Expand All @@ -24,7 +25,7 @@ type ContainerProps = {
export const ChapterFooter: React.FC<ContainerProps> = (props) => {
const { chapterId, sectionId } = props;

const id = `chapter${chapterId}${sectionId}`;
const id = getIdFromChapterIdAndSectionId(chapterId, sectionId);
const next = getChapterNextSectionUrl(chapterId, sectionId);
const previous = getChapterPreviousSectionUrl(chapterId, sectionId);
const isFavorite = useSelector(selectIsFavorite(id));
Expand Down
5 changes: 5 additions & 0 deletions src/data/user/user.selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ const selfUserState = (state: RootState) => state.user;

export const selectUserState = createSelector(selfUserState, (state) => state);

export const selectCurrentFavoritesView = createSelector(
selfUserState,
(state) => state.currentFavoritesView,
);

export const selectCurrentSearchView = createSelector(
selfUserState,
(state) => state.currentSearchView,
Expand Down
28 changes: 15 additions & 13 deletions src/data/user/user.slice.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

interface CurrentSearchView {
currentSearchView: 'card' | 'list';
}
type CurrentView = 'card' | 'list';

interface CurrentSearchViewPayload {
currentSearchView: 'card' | 'list';
interface CurrentViewPayload {
currentView: CurrentView;
}

interface CurrentTheme {
Expand All @@ -19,11 +17,13 @@ interface CurrentThemePayload {
type UserState = {
favorites: Array<string>;
hasSeenTutorial: boolean;
} & CurrentSearchView &
CurrentTheme;
currentFavoritesView: CurrentView;
currentSearchView: CurrentView;
} & CurrentTheme;

const initialState: UserState = {
favorites: [],
currentFavoritesView: 'card',
currentSearchView: 'card',
currentTheme: 'system',
hasSeenTutorial: false,
Expand All @@ -44,12 +44,13 @@ const userSlice = createSlice({
resetUserState() {
return initialState;
},
setCurrentSearchView(
state,
action: PayloadAction<CurrentSearchViewPayload>,
) {
const { currentSearchView } = action.payload;
state.currentSearchView = currentSearchView;
setCurrentFavoritesView(state, action: PayloadAction<CurrentViewPayload>) {
const { currentView } = action.payload;
state.currentFavoritesView = currentView;
},
setCurrentSearchView(state, action: PayloadAction<CurrentViewPayload>) {
const { currentView } = action.payload;
state.currentSearchView = currentView;
},
setCurrentTheme(state, action: PayloadAction<CurrentThemePayload>) {
const { currentTheme } = action.payload;
Expand All @@ -69,6 +70,7 @@ export const {
addFavorite,
removeFavorite,
resetUserState,
setCurrentFavoritesView,
setCurrentSearchView,
setCurrentTheme,
setHasSeenTutorial,
Expand Down
44 changes: 44 additions & 0 deletions src/pages/Favorites/FavoritesPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {
IonButtons,
IonContent,
IonHeader,
IonMenuButton,
IonPage,
IonTitle,
IonToolbar,
} from '@ionic/react';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { FavoritesPopover } from './FavoritesPopover';
import { FavoritesResults } from './FavoritesResults';

export const FavoritesPage: React.FC = () => {
const { t } = useTranslation();

return (
<IonPage>
<IonHeader>
<IonToolbar color="primary">
<IonButtons slot="start">
<IonMenuButton />
</IonButtons>
<IonTitle>{t('FAVORITES.TITLE')}</IonTitle>
<IonButtons slot="primary">
<FavoritesPopover />
</IonButtons>
</IonToolbar>
</IonHeader>

<IonContent color="primary-collapse-condense" fullscreen>
<IonHeader collapse="condense">
<IonToolbar color="primary">
<IonTitle size="large">{t('FAVORITES.TITLE')}</IonTitle>
</IonToolbar>
</IonHeader>
<div className="app-background app-fullscreen">
<FavoritesResults />
</div>
</IonContent>
</IonPage>
);
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React, { useState } from 'react';
import {
IonPopover,
IonButton,
Expand All @@ -14,18 +13,18 @@ import { ellipsisHorizontal, ellipsisVertical } from 'ionicons/icons';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { setCurrentSearchView } from '../../data/user/user.slice';
import { selectCurrentSearchView } from '../../data/user/user.selector';
import { selectCurrentFavoritesView } from '../../data/user/user.selector';
import { setCurrentFavoritesView } from '../../data/user/user.slice';

export const SearchPopover: React.FC = () => {
export const FavoritesPopover: React.FC = () => {
const { t } = useTranslation();
const [popoverState, setShowPopover] = useState({
showPopover: false,
event: undefined,
});

const dispatch = useDispatch();
const currentSearchView = useSelector(selectCurrentSearchView);
const { t } = useTranslation();
const currentView = useSelector(selectCurrentFavoritesView);

return (
<>
Expand All @@ -39,22 +38,22 @@ export const SearchPopover: React.FC = () => {
>
<IonList className={'active'}>
<IonRadioGroup
value={currentSearchView}
onIonChange={(e) =>
value={currentView}
onIonChange={(event) =>
dispatch(
setCurrentSearchView({ currentSearchView: e.detail.value }),
setCurrentFavoritesView({ currentView: event.detail.value }),
)
}
>
<IonListHeader>
<IonLabel>{t('SEARCH.POPOVER.VIEW.TITLE')}</IonLabel>
<IonLabel>{t('FAVORITES.POPOVER.VIEW.TITLE')}</IonLabel>
</IonListHeader>
<IonItem>
<IonLabel>{t('SEARCH.POPOVER.VIEW.ITEM.CARD')}</IonLabel>
<IonLabel>{t('FAVORITES.POPOVER.VIEW.ITEM.CARD')}</IonLabel>
<IonRadio slot="end" value="card" />
</IonItem>
<IonItem lines="none">
<IonLabel>{t('SEARCH.POPOVER.VIEW.ITEM.LIST')}</IonLabel>
<IonLabel>{t('FAVORITES.POPOVER.VIEW.ITEM.LIST')}</IonLabel>
<IonRadio slot="end" value="list" />
</IonItem>
</IonRadioGroup>
Expand Down
48 changes: 48 additions & 0 deletions src/pages/Favorites/FavoritesResults.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { IonGrid, IonNote, IonRow } from '@ionic/react';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { Chapter } from '../../components/Chapters';
import {
selectCurrentFavoritesView,
selectFavorites,
} from '../../data/user/user.selector';
import {
ChapterId,
getChapterIdAndSectionIdFromId,
} from '../../utils/chapters';

export const FavoritesResults: React.FC = () => {
const { t } = useTranslation();
const currentView = useSelector(selectCurrentFavoritesView);
const results = useSelector(selectFavorites);

if (results.length === 0) {
return (
<IonGrid>
<IonRow class="ion-justify-content-center">
<IonNote class="ion-padding">{t('FAVORITES.RESULTS.NONE')}</IonNote>
</IonRow>
</IonGrid>
);
}

return (
<IonGrid>
{results.map((resultId, resultIndex) => {
const { chapterId, sectionId } = getChapterIdAndSectionIdFromId(
resultId,
);
return (
<IonRow class="ion-justify-content-center" key={resultIndex}>
<Chapter
chapterId={chapterId as ChapterId}
sectionId={sectionId}
view={currentView}
/>
</IonRow>
);
})}
</IonGrid>
);
};
1 change: 1 addition & 0 deletions src/pages/Favorites/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { FavoritesPage } from './FavoritesPage';
16 changes: 16 additions & 0 deletions src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3048,6 +3048,22 @@
}
},

"FAVORITES": {
"TITLE": "Favorites",
"POPOVER": {
"VIEW": {
"TITLE": "View",
"ITEM": {
"CARD": "Card",
"LIST": "List"
}
}
},
"RESULTS": {
"NONE": "No article marked as favorite yet. Mark an article as a favorite to list it here."
}
},

"HOME": {
"TITLE": "Home"
},
Expand Down
16 changes: 16 additions & 0 deletions src/utils/chapters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ export const getChapterIdsByUrl = (url: string) => {
return { id: '', subId: '' };
};

export const getChapterIdAndSectionIdFromId = (id: string) => {
// chapter-section-id: 'chapter-01-01'
if (id.startsWith('chapter-')) {
const [chapterId, sectionId] = id.split('-').slice(1);
return { chapterId, sectionId };
}
return { chapterId: '', sectionId: '' };
};

export const getChapterNextSectionUrl = (
chapterId: ChapterId,
sectionId: string,
Expand Down Expand Up @@ -122,3 +131,10 @@ export const getChapterPreviousSectionUrl = (
);
return chapterUrl;
};

export const getIdFromChapterIdAndSectionId = (
chapterId: ChapterId,
sectionId: string,
) => {
return `chapter-${chapterId}-${sectionId}`;
};
9 changes: 6 additions & 3 deletions src/utils/hooks/useChapterSection.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { book, bookmark } from 'ionicons/icons';
import { useTranslation } from 'react-i18next';
import { ChapterId, getChapterColor } from '../chapters';
import {
ChapterId,
getIdFromChapterIdAndSectionId,
getChapterColor,
} from '../chapters';
import { innerText } from '../innerText';
import { getChapterSectionContent } from './useChapterSectionContent';

Expand All @@ -16,8 +20,7 @@ export const useChapterSection = (chapterId: ChapterId, sectionId: string) => {
return {
color,
icon: isHeader ? book : bookmark,
id: `chapter${chapterId}${sectionId}`,
// image: t(`CHAPTER.${chapterId}.${sectionId}.IMAGE.01.FILENAME`),
id: getIdFromChapterIdAndSectionId(chapterId, sectionId),
image: {
original: t(`CHAPTER.${chapterId}.${sectionId}.IMAGE.01.FILENAME`),
thumbnail: {
Expand Down
16 changes: 15 additions & 1 deletion src/utils/hooks/useMenus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
information,
searchOutline,
settingsOutline,
starOutline,
} from 'ionicons/icons';
import { useTranslation } from 'react-i18next';
import {
Expand All @@ -31,10 +32,11 @@ export const useMenus = (): Menu[] => {
const about = useMenuAbout();
const home = useMenuHome();
const search = useMenuSearch();
const favorites = useMenuFavorites();
const settings = useMenuSettings();
const chapters = useMenuChapters();

return [home, about, search, settings, ...chapters.flat()];
return [home, about, search, favorites, settings, ...chapters.flat()];
};

const useMenuAbout = (): Menu => {
Expand Down Expand Up @@ -73,6 +75,18 @@ export const useMenuChapters = (): MenuChapter[][] => {
return chapterMenus;
};

const useMenuFavorites = (): Menu => {
const { t } = useTranslation();

return {
color: '',
icon: starOutline,
isHeader: false,
title: t('FAVORITES.TITLE'),
url: '/favorites',
};
};

const useMenuHome = (): Menu => {
const { t } = useTranslation();

Expand Down

0 comments on commit c82cdbe

Please sign in to comment.