From 5432150414c07b6f1279fa309b40c8e701a599ff Mon Sep 17 00:00:00 2001 From: lukasbicus Date: Wed, 13 Nov 2024 09:16:13 +0100 Subject: [PATCH 01/18] Update plan.md --- plan.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plan.md b/plan.md index 9dd9141..e5ba51f 100644 --- a/plan.md +++ b/plan.md @@ -42,8 +42,8 @@ Features: - [x] **display list of cards** - [ ] display grid of cards - [x] **filter collection based on search** - - [ ] filter collection based on favorites - - [ ] _implement (un) mark favorite_ + - [x] filter collection based on favorites + - [x] _implement (un) mark favorite_ - [x] **implement navigation to card detail** - [x] **Card detail page** - [x] **display page with bar / qr code** @@ -76,3 +76,4 @@ Features: # bugs - [x] company icons are not available in offline mode +- [ ] prefer cameras in environment direction From aff8976db7bc08ec270fe4a68e5e7419cae02e5a Mon Sep 17 00:00:00 2001 From: lukasbicus Date: Wed, 13 Nov 2024 09:22:32 +0100 Subject: [PATCH 02/18] Use enum AppActionTypes --- app/card/card-detail-page.tsx | 3 ++- app/create-card/create-card-form.tsx | 3 ++- app/import-data/import-data-page.tsx | 3 ++- app/lib/app-state/reducer.spec.ts | 12 ++++++++---- app/lib/app-state/reducer.ts | 23 +++++++++++++++-------- 5 files changed, 29 insertions(+), 15 deletions(-) diff --git a/app/card/card-detail-page.tsx b/app/card/card-detail-page.tsx index ca102a7..d6bd741 100644 --- a/app/card/card-detail-page.tsx +++ b/app/card/card-detail-page.tsx @@ -1,5 +1,6 @@ 'use client'; +import { AppActionTypes } from '@/app/lib/app-state/reducer'; import { CodePicture } from '@/app/ui/code-picture'; import { ConfirmDialog } from '@/app/ui/confirm-dialog'; import useAppState from '@/app/lib/app-state/app-state'; @@ -113,7 +114,7 @@ export function CardDetailPage() { confirmButtonLabel="Delete" onConfirmButtonClick={() => { dispatch({ - type: 'DELETE_CARD', + type: AppActionTypes.DeleteCard, payload: { id: card.id }, }); router.replace(Routes.MyCards); diff --git a/app/create-card/create-card-form.tsx b/app/create-card/create-card-form.tsx index 94009d4..e6f3a12 100644 --- a/app/create-card/create-card-form.tsx +++ b/app/create-card/create-card-form.tsx @@ -10,6 +10,7 @@ import { } from '@/app/create-card/createCardFormReducer'; import useAppState from '@/app/lib/app-state/app-state'; import { mapHtml5QrcodeFormatToJsbarcodeFormat } from '@/app/lib/app-state/codeFormat'; +import { AppActionTypes } from '@/app/lib/app-state/reducer'; import { predefinedCompanies } from '@/app/lib/predefined-companies'; import { CardIcon, @@ -138,7 +139,7 @@ export default function CreateCardForm() { className="px-4 py-6 w-full h-full" onSubmit={handleSubmit(data => { appDispatch({ - type: 'ADD_CARD', + type: AppActionTypes.AddCard, payload: { name: data[CreateCardFormNames.Name], code: data[CreateCardFormNames.Code], diff --git a/app/import-data/import-data-page.tsx b/app/import-data/import-data-page.tsx index ed3d90f..4bea7c5 100644 --- a/app/import-data/import-data-page.tsx +++ b/app/import-data/import-data-page.tsx @@ -6,6 +6,7 @@ import { ParseCardsErrors, } from '@/app/import-data/utils'; import useAppState from '@/app/lib/app-state/app-state'; +import { AppActionTypes } from '@/app/lib/app-state/reducer'; import { Routes } from '@/app/lib/shared'; import { ConfirmDialog } from '@/app/ui/confirm-dialog'; import { useErrorDialog } from '@/app/ui/error-dialog-context'; @@ -53,7 +54,7 @@ export function ImportDataPage() { }); } else { dispatch({ - type: 'IMPORT_CARDS', + type: AppActionTypes.ImportCards, payload: uniqParsedCards, }); // display count of added records in dialog diff --git a/app/lib/app-state/reducer.spec.ts b/app/lib/app-state/reducer.spec.ts index e189f3c..ae2cc22 100644 --- a/app/lib/app-state/reducer.spec.ts +++ b/app/lib/app-state/reducer.spec.ts @@ -1,6 +1,7 @@ import { mapHtml5QrcodeFormatToJsbarcodeFormat } from '@/app/lib/app-state/codeFormat'; import { AddCardAction, + AppActionTypes, appReducer, Card, DeleteCardAction, @@ -35,7 +36,10 @@ describe('appReducer', () => { name: 'Test Card', code: 'ABC123', }; - const addAction: AddCardAction = { type: 'ADD_CARD', payload: newCard }; + const addAction: AddCardAction = { + type: AppActionTypes.AddCard, + payload: newCard, + }; const state = appReducer(initialState, addAction); expect(state.cards).toHaveLength(1); @@ -50,7 +54,7 @@ describe('appReducer', () => { }; const updatedCard = { ...initialState.cards[0], name: 'Updated Card' }; const editAction: EditCardAction = { - type: 'EDIT_CARD', + type: AppActionTypes.EditCard, payload: { id: 'card1', updatedCard }, }; @@ -69,7 +73,7 @@ describe('appReducer', () => { ], }; const deleteAction: DeleteCardAction = { - type: 'DELETE_CARD', + type: AppActionTypes.DeleteCard, payload: { id: 'card1' }, }; @@ -109,7 +113,7 @@ describe('appReducer', () => { }, ]; const addAction: ImportCardsAction = { - type: 'IMPORT_CARDS', + type: AppActionTypes.ImportCards, payload: parsedCards, }; diff --git a/app/lib/app-state/reducer.ts b/app/lib/app-state/reducer.ts index 162760e..e9b7abb 100644 --- a/app/lib/app-state/reducer.ts +++ b/app/lib/app-state/reducer.ts @@ -12,27 +12,34 @@ export type Card = { codeFormat: string; }; +export enum AppActionTypes { + AddCard = 'addCard', + EditCard = 'editCard', + DeleteCard = 'deleteCard', + ImportCards = 'importCards', +} + export type AppState = { cards: Card[]; }; export type AddCardAction = { - type: 'ADD_CARD'; + type: AppActionTypes.AddCard; payload: Omit; }; export type EditCardAction = { - type: 'EDIT_CARD'; + type: AppActionTypes.EditCard; payload: { id: string; updatedCard: Card }; }; export type DeleteCardAction = { - type: 'DELETE_CARD'; + type: AppActionTypes.DeleteCard; payload: { id: string }; }; export type ImportCardsAction = { - type: 'IMPORT_CARDS'; + type: AppActionTypes.ImportCards; payload: Omit[]; }; @@ -51,12 +58,12 @@ export const appReducer = ( action: AppActions ): AppState => { switch (action.type) { - case 'ADD_CARD': + case AppActionTypes.AddCard: return { ...state, cards: [...state.cards, { ...action.payload, id: uuid() }], }; - case 'EDIT_CARD': + case AppActionTypes.EditCard: return { ...state, cards: state.cards.map(card => @@ -65,12 +72,12 @@ export const appReducer = ( : card ), }; - case 'DELETE_CARD': + case AppActionTypes.DeleteCard: return { ...state, cards: state.cards.filter(card => card.id !== action.payload.id), }; - case 'IMPORT_CARDS': + case AppActionTypes.ImportCards: return { ...state, cards: state.cards.concat( From 21ed5c48659ef51d9463777b694274224bb774ee Mon Sep 17 00:00:00 2001 From: lukasbicus Date: Wed, 13 Nov 2024 09:22:45 +0100 Subject: [PATCH 03/18] Remove unused page scan-card. --- app/scan-card/page.tsx | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 app/scan-card/page.tsx diff --git a/app/scan-card/page.tsx b/app/scan-card/page.tsx deleted file mode 100644 index c749c5b..0000000 --- a/app/scan-card/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Page() { - return
Scan card page
; -} From 8a4f0322509c594e74f4fb9aff031419214f1056 Mon Sep 17 00:00:00 2001 From: lukasbicus Date: Wed, 13 Nov 2024 09:35:00 +0100 Subject: [PATCH 04/18] Implement mark favorite action. --- app/lib/app-state/reducer.spec.ts | 30 ++++++++++++++++++++++++++++++ app/lib/app-state/reducer.ts | 18 +++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/app/lib/app-state/reducer.spec.ts b/app/lib/app-state/reducer.spec.ts index ae2cc22..9e2c22f 100644 --- a/app/lib/app-state/reducer.spec.ts +++ b/app/lib/app-state/reducer.spec.ts @@ -7,6 +7,7 @@ import { DeleteCardAction, EditCardAction, ImportCardsAction, + ToggleCardFavoriteAction, } from '@/app/lib/app-state/reducer'; import { CardIcon } from '@/app/lib/shared'; import { describe, expect, it } from '@jest/globals'; @@ -122,4 +123,33 @@ describe('appReducer', () => { expect(state.cards[0]).toMatchObject(parsedCards[0]); expect(state.cards[1]).toMatchObject(parsedCards[1]); }); + + it('should handle ToggleCardFavoriteAction', () => { + const initialCard = { + bgColor: '#4523C9', + icon: CardIcon.Retail, + codeFormat: mapHtml5QrcodeFormatToJsbarcodeFormat( + Html5QrcodeSupportedFormats.QR_CODE + ), + id: uuid(), + name: 'Test Card', + code: 'ABC123', + }; + const initialState: AppState = { + cards: [initialCard], + }; + + const addAction: ToggleCardFavoriteAction = { + type: AppActionTypes.ToggleCardFavorite, + payload: { + id: initialState.cards[0].id, + }, + }; + + const state1 = appReducer(initialState, addAction); + expect(state1.cards[0]).toMatchObject({ ...initialCard, favorite: true }); + + const state2 = appReducer(state1, addAction); + expect(state2.cards[0]).toMatchObject({ ...initialCard, favorite: false }); + }); }); diff --git a/app/lib/app-state/reducer.ts b/app/lib/app-state/reducer.ts index e9b7abb..e4422e0 100644 --- a/app/lib/app-state/reducer.ts +++ b/app/lib/app-state/reducer.ts @@ -17,6 +17,7 @@ export enum AppActionTypes { EditCard = 'editCard', DeleteCard = 'deleteCard', ImportCards = 'importCards', + ToggleCardFavorite = 'toggleCardFavorite', } export type AppState = { @@ -43,11 +44,17 @@ export type ImportCardsAction = { payload: Omit[]; }; +export type ToggleCardFavoriteAction = { + type: AppActionTypes.ToggleCardFavorite; + payload: { id: string }; +}; + export type AppActions = | AddCardAction | EditCardAction | DeleteCardAction - | ImportCardsAction; + | ImportCardsAction + | ToggleCardFavoriteAction; export const initialState: AppState = { cards: [], @@ -87,6 +94,15 @@ export const appReducer = ( })) ), }; + case AppActionTypes.ToggleCardFavorite: + return { + ...state, + cards: state.cards.map(card => + card.id !== action.payload.id + ? card + : { ...card, favorite: !card.favorite } + ), + }; default: return state; } From 43ba3963065ddd573e57d2d81c41ab7178e790ce Mon Sep 17 00:00:00 2001 From: lukasbicus Date: Wed, 13 Nov 2024 09:35:08 +0100 Subject: [PATCH 05/18] Cleanup in reducer.spec.ts --- app/lib/app-state/reducer.spec.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/lib/app-state/reducer.spec.ts b/app/lib/app-state/reducer.spec.ts index 9e2c22f..f060d80 100644 --- a/app/lib/app-state/reducer.spec.ts +++ b/app/lib/app-state/reducer.spec.ts @@ -3,6 +3,7 @@ import { AddCardAction, AppActionTypes, appReducer, + AppState, Card, DeleteCardAction, EditCardAction, @@ -26,7 +27,7 @@ const dummyCard: Card = { }; describe('appReducer', () => { - it('should handle ADD_CARD', () => { + it('should handle AddCardAction', () => { const initialState = { cards: [] }; const newCard: Omit = { bgColor: '#4523C9', @@ -47,7 +48,7 @@ describe('appReducer', () => { expect(state.cards[0]).toMatchObject(newCard); }); - it('should handle EDIT_CARD', () => { + it('should handle EditCardAction', () => { const initialState = { cards: [ { ...dummyCard, name: 'Initial Card', id: 'card1', code: 'ABC123' }, @@ -67,7 +68,7 @@ describe('appReducer', () => { }); }); - it('should handle DELETE_CARD', () => { + it('should handle DeleteCardAction', () => { const initialState = { cards: [ { ...dummyCard, name: 'Card to Delete', id: 'card1', code: 'ABC123' }, @@ -91,7 +92,7 @@ describe('appReducer', () => { expect(state).toEqual(initialState); }); - it('should handle IMPORT_CARDS', () => { + it('should handle ImportCardsAction', () => { const initialState = { cards: [] }; const parsedCards: Omit[] = [ { @@ -113,12 +114,12 @@ describe('appReducer', () => { code: 'XS78DB', }, ]; - const addAction: ImportCardsAction = { + const importAction: ImportCardsAction = { type: AppActionTypes.ImportCards, payload: parsedCards, }; - const state = appReducer(initialState, addAction); + const state = appReducer(initialState, importAction); expect(state.cards).toHaveLength(2); expect(state.cards[0]).toMatchObject(parsedCards[0]); expect(state.cards[1]).toMatchObject(parsedCards[1]); From dd359d14a0bb0b53211afed296b22eff70b9e3ea Mon Sep 17 00:00:00 2001 From: lukasbicus Date: Wed, 13 Nov 2024 09:49:31 +0100 Subject: [PATCH 06/18] Implement mark favorite --- app/(homescreens)/my-cards/my-cards.tsx | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/app/(homescreens)/my-cards/my-cards.tsx b/app/(homescreens)/my-cards/my-cards.tsx index b747747..605a62d 100644 --- a/app/(homescreens)/my-cards/my-cards.tsx +++ b/app/(homescreens)/my-cards/my-cards.tsx @@ -1,17 +1,18 @@ 'use client'; import useAppState from '@/app/lib/app-state/app-state'; +import { AppActionTypes } from '@/app/lib/app-state/reducer'; import { filterByQuery } from '@/app/lib/filterByQuery'; import { Routes } from '@/app/lib/shared'; -import { IconStar } from '@tabler/icons-react'; import { CompanyIcon } from '@/app/ui/company-icon'; +import { IconStar, IconStarFilled } from '@tabler/icons-react'; import Link from 'next/link'; import { useSearchParams } from 'next/navigation'; export default function MyCards() { const searchParams = useSearchParams(); const query = searchParams.get('query')?.toString(); - const [state] = useAppState(); + const [state, dispatch] = useAppState(); return (
    @@ -35,8 +36,23 @@ export default function MyCards() { {card.name} - From 1a457ab00af6559681fd7f7747c063092db45cf8 Mon Sep 17 00:00:00 2001 From: lukasbicus Date: Wed, 13 Nov 2024 09:53:51 +0100 Subject: [PATCH 07/18] Rename filterByQuery.ts to filter.ts --- app/(homescreens)/add-cards/predefined-companies-list.tsx | 2 +- app/(homescreens)/my-cards/my-cards.tsx | 2 +- app/lib/{filterByQuery.ts => filters.ts} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename app/lib/{filterByQuery.ts => filters.ts} (100%) diff --git a/app/(homescreens)/add-cards/predefined-companies-list.tsx b/app/(homescreens)/add-cards/predefined-companies-list.tsx index d8d80d9..cf61660 100644 --- a/app/(homescreens)/add-cards/predefined-companies-list.tsx +++ b/app/(homescreens)/add-cards/predefined-companies-list.tsx @@ -1,6 +1,6 @@ 'use client'; -import { filterByQuery } from '@/app/lib/filterByQuery'; +import { filterByQuery } from '@/app/lib/filters'; import { predefinedCompanies } from '@/app/lib/predefined-companies'; import { Routes } from '@/app/lib/shared'; import Image from 'next/image'; diff --git a/app/(homescreens)/my-cards/my-cards.tsx b/app/(homescreens)/my-cards/my-cards.tsx index 605a62d..8c9003c 100644 --- a/app/(homescreens)/my-cards/my-cards.tsx +++ b/app/(homescreens)/my-cards/my-cards.tsx @@ -2,7 +2,7 @@ import useAppState from '@/app/lib/app-state/app-state'; import { AppActionTypes } from '@/app/lib/app-state/reducer'; -import { filterByQuery } from '@/app/lib/filterByQuery'; +import { filterByQuery } from '@/app/lib/filters'; import { Routes } from '@/app/lib/shared'; import { CompanyIcon } from '@/app/ui/company-icon'; import { IconStar, IconStarFilled } from '@tabler/icons-react'; diff --git a/app/lib/filterByQuery.ts b/app/lib/filters.ts similarity index 100% rename from app/lib/filterByQuery.ts rename to app/lib/filters.ts From 03e5cefcdf4045a65be61d1fc991cc36b9dbc21d Mon Sep 17 00:00:00 2001 From: lukasbicus Date: Wed, 13 Nov 2024 09:57:45 +0100 Subject: [PATCH 08/18] Add favoriteFilter function --- app/lib/filters.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/lib/filters.ts b/app/lib/filters.ts index 542eeb5..d0cf3af 100644 --- a/app/lib/filters.ts +++ b/app/lib/filters.ts @@ -10,3 +10,9 @@ export function filterByQuery( const q = deburr(query.toLowerCase()); return cards.filter(card => deburr(card.name.toLowerCase()).includes(q)); } + +export function favoriteFilter( + o: T +): boolean { + return Boolean(o.favorite); +} From a394eb6eed0207bccc8c3892a7616bea1c37e1c2 Mon Sep 17 00:00:00 2001 From: lukasbicus Date: Wed, 13 Nov 2024 10:11:24 +0100 Subject: [PATCH 09/18] Implement ToggleShowFavoritesOnlyAction --- app/lib/app-state/reducer.spec.ts | 29 +++++++++++++++++++++++++---- app/lib/app-state/reducer.ts | 11 ++++++++++- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/app/lib/app-state/reducer.spec.ts b/app/lib/app-state/reducer.spec.ts index f060d80..2299a73 100644 --- a/app/lib/app-state/reducer.spec.ts +++ b/app/lib/app-state/reducer.spec.ts @@ -9,6 +9,7 @@ import { EditCardAction, ImportCardsAction, ToggleCardFavoriteAction, + ToggleShowFavoritesOnlyAction, } from '@/app/lib/app-state/reducer'; import { CardIcon } from '@/app/lib/shared'; import { describe, expect, it } from '@jest/globals'; @@ -28,7 +29,7 @@ const dummyCard: Card = { describe('appReducer', () => { it('should handle AddCardAction', () => { - const initialState = { cards: [] }; + const initialState: AppState = { cards: [], showFavoritesOnly: false }; const newCard: Omit = { bgColor: '#4523C9', icon: CardIcon.Retail, @@ -49,10 +50,11 @@ describe('appReducer', () => { }); it('should handle EditCardAction', () => { - const initialState = { + const initialState: AppState = { cards: [ { ...dummyCard, name: 'Initial Card', id: 'card1', code: 'ABC123' }, ], + showFavoritesOnly: false, }; const updatedCard = { ...initialState.cards[0], name: 'Updated Card' }; const editAction: EditCardAction = { @@ -69,10 +71,11 @@ describe('appReducer', () => { }); it('should handle DeleteCardAction', () => { - const initialState = { + const initialState: AppState = { cards: [ { ...dummyCard, name: 'Card to Delete', id: 'card1', code: 'ABC123' }, ], + showFavoritesOnly: false, }; const deleteAction: DeleteCardAction = { type: AppActionTypes.DeleteCard, @@ -93,7 +96,7 @@ describe('appReducer', () => { }); it('should handle ImportCardsAction', () => { - const initialState = { cards: [] }; + const initialState: AppState = { cards: [], showFavoritesOnly: false }; const parsedCards: Omit[] = [ { bgColor: '#4523C9', @@ -138,6 +141,7 @@ describe('appReducer', () => { }; const initialState: AppState = { cards: [initialCard], + showFavoritesOnly: false, }; const addAction: ToggleCardFavoriteAction = { @@ -153,4 +157,21 @@ describe('appReducer', () => { const state2 = appReducer(state1, addAction); expect(state2.cards[0]).toMatchObject({ ...initialCard, favorite: false }); }); + + it('should handle ToggleShowFavoritesOnlyAction', () => { + const initialState: AppState = { + cards: [], + showFavoritesOnly: false, + }; + + const addAction: ToggleShowFavoritesOnlyAction = { + type: AppActionTypes.ToggleShowFavoritesOnly, + }; + + const state1 = appReducer(initialState, addAction); + expect(state1).toMatchObject({ ...initialState, showFavoritesOnly: true }); + + const state2 = appReducer(state1, addAction); + expect(state2).toMatchObject({ ...state1, showFavoritesOnly: false }); + }); }); diff --git a/app/lib/app-state/reducer.ts b/app/lib/app-state/reducer.ts index e4422e0..6c0c831 100644 --- a/app/lib/app-state/reducer.ts +++ b/app/lib/app-state/reducer.ts @@ -18,10 +18,12 @@ export enum AppActionTypes { DeleteCard = 'deleteCard', ImportCards = 'importCards', ToggleCardFavorite = 'toggleCardFavorite', + ToggleShowFavoritesOnly = 'toggleShowFavoritesOnly', } export type AppState = { cards: Card[]; + showFavoritesOnly: boolean; }; export type AddCardAction = { @@ -48,16 +50,21 @@ export type ToggleCardFavoriteAction = { type: AppActionTypes.ToggleCardFavorite; payload: { id: string }; }; +export type ToggleShowFavoritesOnlyAction = { + type: AppActionTypes.ToggleShowFavoritesOnly; +}; export type AppActions = | AddCardAction | EditCardAction | DeleteCardAction | ImportCardsAction - | ToggleCardFavoriteAction; + | ToggleCardFavoriteAction + | ToggleShowFavoritesOnlyAction; export const initialState: AppState = { cards: [], + showFavoritesOnly: false, }; export const appReducer = ( @@ -103,6 +110,8 @@ export const appReducer = ( : { ...card, favorite: !card.favorite } ), }; + case AppActionTypes.ToggleShowFavoritesOnly: + return { ...state, showFavoritesOnly: !state.showFavoritesOnly }; default: return state; } From b1701a2e5cb64ec164173c13988600a4dd701901 Mon Sep 17 00:00:00 2001 From: lukasbicus Date: Wed, 13 Nov 2024 10:13:43 +0100 Subject: [PATCH 10/18] Add plan to my cards --- app/(homescreens)/my-cards/my-cards.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/(homescreens)/my-cards/my-cards.tsx b/app/(homescreens)/my-cards/my-cards.tsx index 8c9003c..683fc5f 100644 --- a/app/(homescreens)/my-cards/my-cards.tsx +++ b/app/(homescreens)/my-cards/my-cards.tsx @@ -14,6 +14,11 @@ export default function MyCards() { const query = searchParams.get('query')?.toString(); const [state, dispatch] = useAppState(); + // add showFavoritesOnly to app state + // implement toggle on showFavoritesOnly + // if showFavoritesOnly is valid, filter cards by favoriteFilter + // if query, filter cards by queryFilter + return (
      {filterByQuery(state.cards, query).map(card => ( From 014726d318cf6ce7d16f5c3ddbe0ceae0feab0a4 Mon Sep 17 00:00:00 2001 From: lukasbicus Date: Wed, 13 Nov 2024 10:21:21 +0100 Subject: [PATCH 11/18] Move my cards page content to my card comp. --- app/(homescreens)/my-cards/my-cards.tsx | 119 +++++++++++++++--------- app/(homescreens)/my-cards/page.tsx | 38 +------- 2 files changed, 80 insertions(+), 77 deletions(-) diff --git a/app/(homescreens)/my-cards/my-cards.tsx b/app/(homescreens)/my-cards/my-cards.tsx index 683fc5f..3d24856 100644 --- a/app/(homescreens)/my-cards/my-cards.tsx +++ b/app/(homescreens)/my-cards/my-cards.tsx @@ -5,7 +5,14 @@ import { AppActionTypes } from '@/app/lib/app-state/reducer'; import { filterByQuery } from '@/app/lib/filters'; import { Routes } from '@/app/lib/shared'; import { CompanyIcon } from '@/app/ui/company-icon'; -import { IconStar, IconStarFilled } from '@tabler/icons-react'; +import { PageTemplate } from '@/app/ui/page-template'; +import { PrimaryHeader } from '@/app/ui/primary-header'; +import { Search } from '@/app/ui/search'; +import { + IconStar, + IconStarFilled, + IconStarHalfFilled, +} from '@tabler/icons-react'; import Link from 'next/link'; import { useSearchParams } from 'next/navigation'; @@ -20,48 +27,76 @@ export default function MyCards() { // if query, filter cards by queryFilter return ( -
        - {filterByQuery(state.cards, query).map(card => ( -
      • - - - - - {card.name} - + + } + > + + + } + > +
          + {filterByQuery(state.cards, query).map(card => ( +
        • + - {card.favorite ? ( - - ) : ( - - )} - - -
        • - ))} -
        + + + + {card.name} + + +
      • + ))} +
      + ); } diff --git a/app/(homescreens)/my-cards/page.tsx b/app/(homescreens)/my-cards/page.tsx index 3bc75fa..6e5cf03 100644 --- a/app/(homescreens)/my-cards/page.tsx +++ b/app/(homescreens)/my-cards/page.tsx @@ -1,43 +1,11 @@ import MyCards from '@/app/(homescreens)/my-cards/my-cards'; -import { Routes } from '@/app/lib/shared'; import { Loading } from '@/app/ui/loading'; -import { PageTemplate } from '@/app/ui/page-template'; -import { PrimaryHeader } from '@/app/ui/primary-header'; -import { Search } from '@/app/ui/search'; -import { IconLayoutGrid, IconStar } from '@tabler/icons-react'; -import Link from 'next/link'; import { Suspense } from 'react'; export default function Page() { return ( - - - - - - - - - } - > - }> - - - - } - > - }> - - - + }> + + ); } From 5709888ad7c29075468f0ec52c2c09805a8b2b50 Mon Sep 17 00:00:00 2001 From: lukasbicus Date: Wed, 13 Nov 2024 10:45:28 +0100 Subject: [PATCH 12/18] Filter cards by favorite prop. --- app/(homescreens)/my-cards/my-cards.tsx | 22 ++++++++++++++++++---- app/lib/filters.ts | 8 ++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/app/(homescreens)/my-cards/my-cards.tsx b/app/(homescreens)/my-cards/my-cards.tsx index 3d24856..ec78943 100644 --- a/app/(homescreens)/my-cards/my-cards.tsx +++ b/app/(homescreens)/my-cards/my-cards.tsx @@ -1,8 +1,8 @@ 'use client'; import useAppState from '@/app/lib/app-state/app-state'; -import { AppActionTypes } from '@/app/lib/app-state/reducer'; -import { filterByQuery } from '@/app/lib/filters'; +import { AppActionTypes, AppState, Card } from '@/app/lib/app-state/reducer'; +import { favoriteFilter, getNameFilter } from '@/app/lib/filters'; import { Routes } from '@/app/lib/shared'; import { CompanyIcon } from '@/app/ui/company-icon'; import { PageTemplate } from '@/app/ui/page-template'; @@ -13,12 +13,26 @@ import { IconStarFilled, IconStarHalfFilled, } from '@tabler/icons-react'; +import { filter, overEvery } from 'lodash'; import Link from 'next/link'; import { useSearchParams } from 'next/navigation'; +function getVisibleCards(appState: AppState, query: string): Card[] { + const filterFunctions: ((c: Card) => boolean)[] = []; + if (query) { + filterFunctions.push(getNameFilter(query)); + } + if (appState.showFavoritesOnly) { + filterFunctions.push(favoriteFilter); + } + const combinedFilter: (c: Card) => boolean = overEvery(filterFunctions); + + return filter(appState.cards, combinedFilter); +} + export default function MyCards() { const searchParams = useSearchParams(); - const query = searchParams.get('query')?.toString(); + const query = searchParams.get('query')?.toString() ?? ''; const [state, dispatch] = useAppState(); // add showFavoritesOnly to app state @@ -55,7 +69,7 @@ export default function MyCards() { } >
        - {filterByQuery(state.cards, query).map(card => ( + {getVisibleCards(state, query).map(card => (
      • ( cards: T[], query?: string @@ -11,6 +12,13 @@ export function filterByQuery( return cards.filter(card => deburr(card.name.toLowerCase()).includes(q)); } +export function getNameFilter( + query: string +): (o: T) => boolean { + const q = deburr(query.toLowerCase()); + return (o: T) => deburr(o.name.toLowerCase()).includes(q); +} + export function favoriteFilter( o: T ): boolean { From 141ba96644d87c9b01f208d2a8f647930057e701 Mon Sep 17 00:00:00 2001 From: lukasbicus Date: Wed, 13 Nov 2024 10:47:46 +0100 Subject: [PATCH 13/18] Remove unused filterByQuery --- .../add-cards/predefined-companies-list.tsx | 8 ++++++-- app/lib/filters.ts | 12 ------------ 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/app/(homescreens)/add-cards/predefined-companies-list.tsx b/app/(homescreens)/add-cards/predefined-companies-list.tsx index cf61660..66f6af7 100644 --- a/app/(homescreens)/add-cards/predefined-companies-list.tsx +++ b/app/(homescreens)/add-cards/predefined-companies-list.tsx @@ -1,6 +1,6 @@ 'use client'; -import { filterByQuery } from '@/app/lib/filters'; +import { getNameFilter } from '@/app/lib/filters'; import { predefinedCompanies } from '@/app/lib/predefined-companies'; import { Routes } from '@/app/lib/shared'; import Image from 'next/image'; @@ -10,9 +10,13 @@ import { useSearchParams } from 'next/navigation'; export function PredefinedCompaniesList() { const searchParams = useSearchParams(); const query = searchParams.get('query')?.toString(); + + const companies = query + ? predefinedCompanies.filter(getNameFilter(query)) + : predefinedCompanies; return (
          - {filterByQuery(predefinedCompanies, query).map(company => ( + {companies.map(company => (
        • ( - cards: T[], - query?: string -): T[] { - if (!query) { - return cards; - } - const q = deburr(query.toLowerCase()); - return cards.filter(card => deburr(card.name.toLowerCase()).includes(q)); -} - export function getNameFilter( query: string ): (o: T) => boolean { From 2f0abcf85965d8792f0c37c0acebef8d6b2b3478 Mon Sep 17 00:00:00 2001 From: lukasbicus Date: Wed, 13 Nov 2024 10:50:15 +0100 Subject: [PATCH 14/18] Rename my-cards to my-cards-page. --- .../my-cards/{my-cards.tsx => my-cards-page.tsx} | 2 +- app/(homescreens)/my-cards/page.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename app/(homescreens)/my-cards/{my-cards.tsx => my-cards-page.tsx} (98%) diff --git a/app/(homescreens)/my-cards/my-cards.tsx b/app/(homescreens)/my-cards/my-cards-page.tsx similarity index 98% rename from app/(homescreens)/my-cards/my-cards.tsx rename to app/(homescreens)/my-cards/my-cards-page.tsx index ec78943..19f0426 100644 --- a/app/(homescreens)/my-cards/my-cards.tsx +++ b/app/(homescreens)/my-cards/my-cards-page.tsx @@ -30,7 +30,7 @@ function getVisibleCards(appState: AppState, query: string): Card[] { return filter(appState.cards, combinedFilter); } -export default function MyCards() { +export default function MyCardsPage() { const searchParams = useSearchParams(); const query = searchParams.get('query')?.toString() ?? ''; const [state, dispatch] = useAppState(); diff --git a/app/(homescreens)/my-cards/page.tsx b/app/(homescreens)/my-cards/page.tsx index 6e5cf03..6d9376b 100644 --- a/app/(homescreens)/my-cards/page.tsx +++ b/app/(homescreens)/my-cards/page.tsx @@ -1,11 +1,11 @@ -import MyCards from '@/app/(homescreens)/my-cards/my-cards'; +import MyCardsPage from '@/app/(homescreens)/my-cards/my-cards-page'; import { Loading } from '@/app/ui/loading'; import { Suspense } from 'react'; export default function Page() { return ( }> - + ); } From a4db3fe079e4eb140873804f5c206a76d7ed37ca Mon Sep 17 00:00:00 2001 From: lukasbicus Date: Wed, 13 Nov 2024 11:06:25 +0100 Subject: [PATCH 15/18] Extract cards to CardsList comp. --- app/(homescreens)/my-cards/cards-list.tsx | 62 ++++++++++++++++++++ app/(homescreens)/my-cards/my-cards-page.tsx | 56 ++---------------- 2 files changed, 67 insertions(+), 51 deletions(-) create mode 100644 app/(homescreens)/my-cards/cards-list.tsx diff --git a/app/(homescreens)/my-cards/cards-list.tsx b/app/(homescreens)/my-cards/cards-list.tsx new file mode 100644 index 0000000..23a0789 --- /dev/null +++ b/app/(homescreens)/my-cards/cards-list.tsx @@ -0,0 +1,62 @@ +'use client'; + +import { AppActions, AppActionTypes, Card } from '@/app/lib/app-state/reducer'; +import { Routes } from '@/app/lib/shared'; +import { CompanyIcon } from '@/app/ui/company-icon'; +import { IconStar, IconStarFilled } from '@tabler/icons-react'; +import Link from 'next/link'; +import { Dispatch } from 'react'; + +export function CardsList({ + cards, + dispatch, +}: { + cards: Card[]; + dispatch: Dispatch; +}) { + return ( +
            + {cards.map(card => ( +
          • + + + + + {card.name} + + +
          • + ))} +
          + ); +} diff --git a/app/(homescreens)/my-cards/my-cards-page.tsx b/app/(homescreens)/my-cards/my-cards-page.tsx index 19f0426..59ef5d3 100644 --- a/app/(homescreens)/my-cards/my-cards-page.tsx +++ b/app/(homescreens)/my-cards/my-cards-page.tsx @@ -1,20 +1,14 @@ 'use client'; +import { CardsList } from '@/app/(homescreens)/my-cards/cards-list'; import useAppState from '@/app/lib/app-state/app-state'; import { AppActionTypes, AppState, Card } from '@/app/lib/app-state/reducer'; import { favoriteFilter, getNameFilter } from '@/app/lib/filters'; -import { Routes } from '@/app/lib/shared'; -import { CompanyIcon } from '@/app/ui/company-icon'; import { PageTemplate } from '@/app/ui/page-template'; import { PrimaryHeader } from '@/app/ui/primary-header'; import { Search } from '@/app/ui/search'; -import { - IconStar, - IconStarFilled, - IconStarHalfFilled, -} from '@tabler/icons-react'; +import { IconStarFilled, IconStarHalfFilled } from '@tabler/icons-react'; import { filter, overEvery } from 'lodash'; -import Link from 'next/link'; import { useSearchParams } from 'next/navigation'; function getVisibleCards(appState: AppState, query: string): Card[] { @@ -40,6 +34,8 @@ export default function MyCardsPage() { // if showFavoritesOnly is valid, filter cards by favoriteFilter // if query, filter cards by queryFilter + const visibleCards = getVisibleCards(state, query); + return ( } > -
            - {getVisibleCards(state, query).map(card => ( -
          • - - - - - {card.name} - - -
          • - ))} -
          +
          ); } From 2e63608489d31358f89cb2a49b7bc25637b4fe26 Mon Sep 17 00:00:00 2001 From: lukasbicus Date: Wed, 13 Nov 2024 11:31:39 +0100 Subject: [PATCH 16/18] Handle no carts states. --- app/(homescreens)/my-cards/my-cards-page.tsx | 32 ++++++++++++++++++-- app/ui/main-message.tsx | 4 +-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/app/(homescreens)/my-cards/my-cards-page.tsx b/app/(homescreens)/my-cards/my-cards-page.tsx index 59ef5d3..36e8378 100644 --- a/app/(homescreens)/my-cards/my-cards-page.tsx +++ b/app/(homescreens)/my-cards/my-cards-page.tsx @@ -4,11 +4,14 @@ import { CardsList } from '@/app/(homescreens)/my-cards/cards-list'; import useAppState from '@/app/lib/app-state/app-state'; import { AppActionTypes, AppState, Card } from '@/app/lib/app-state/reducer'; import { favoriteFilter, getNameFilter } from '@/app/lib/filters'; +import { Routes } from '@/app/lib/shared'; +import { MainMessage } from '@/app/ui/main-message'; import { PageTemplate } from '@/app/ui/page-template'; import { PrimaryHeader } from '@/app/ui/primary-header'; import { Search } from '@/app/ui/search'; import { IconStarFilled, IconStarHalfFilled } from '@tabler/icons-react'; import { filter, overEvery } from 'lodash'; +import Link from 'next/link'; import { useSearchParams } from 'next/navigation'; function getVisibleCards(appState: AppState, query: string): Card[] { @@ -60,11 +63,36 @@ export default function MyCardsPage() { } > - + {state.cards.length !== 0 ? ( + + ) : ( +
          + )} } > - + {state.cards.length === 0 ? ( + +
          + + Add cards + + + Import cards + +
          +
          + ) : visibleCards.length === 0 ? ( + + ) : ( + + )} ); } diff --git a/app/ui/main-message.tsx b/app/ui/main-message.tsx index 0aa5ed0..661e84b 100644 --- a/app/ui/main-message.tsx +++ b/app/ui/main-message.tsx @@ -13,8 +13,8 @@ export function MainMessage({ }) { return (
          -

          {title}

          -

          {description}

          +

          {title}

          +

          {description}

          {children}
          ); From 61c2bbb2b307db39e80475d5ee15de5148b27cc7 Mon Sep 17 00:00:00 2001 From: lukasbicus Date: Wed, 13 Nov 2024 11:33:51 +0100 Subject: [PATCH 17/18] Add a bug --- plan.md | 1 + 1 file changed, 1 insertion(+) diff --git a/plan.md b/plan.md index e5ba51f..8879627 100644 --- a/plan.md +++ b/plan.md @@ -77,3 +77,4 @@ Features: - [x] company icons are not available in offline mode - [ ] prefer cameras in environment direction +- [ ] move app state to the context From 3d77b1618ad352fd4ad4e5e456557ce2a93fa9c8 Mon Sep 17 00:00:00 2001 From: lukasbicus Date: Wed, 13 Nov 2024 11:36:29 +0100 Subject: [PATCH 18/18] Adjust names. --- app/lib/app-state/reducer.spec.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/lib/app-state/reducer.spec.ts b/app/lib/app-state/reducer.spec.ts index 2299a73..6b99eaf 100644 --- a/app/lib/app-state/reducer.spec.ts +++ b/app/lib/app-state/reducer.spec.ts @@ -144,17 +144,17 @@ describe('appReducer', () => { showFavoritesOnly: false, }; - const addAction: ToggleCardFavoriteAction = { + const toggleCardFavoriteAction: ToggleCardFavoriteAction = { type: AppActionTypes.ToggleCardFavorite, payload: { id: initialState.cards[0].id, }, }; - const state1 = appReducer(initialState, addAction); + const state1 = appReducer(initialState, toggleCardFavoriteAction); expect(state1.cards[0]).toMatchObject({ ...initialCard, favorite: true }); - const state2 = appReducer(state1, addAction); + const state2 = appReducer(state1, toggleCardFavoriteAction); expect(state2.cards[0]).toMatchObject({ ...initialCard, favorite: false }); }); @@ -164,14 +164,14 @@ describe('appReducer', () => { showFavoritesOnly: false, }; - const addAction: ToggleShowFavoritesOnlyAction = { + const toggleShowFavoritesOnlyAction: ToggleShowFavoritesOnlyAction = { type: AppActionTypes.ToggleShowFavoritesOnly, }; - const state1 = appReducer(initialState, addAction); + const state1 = appReducer(initialState, toggleShowFavoritesOnlyAction); expect(state1).toMatchObject({ ...initialState, showFavoritesOnly: true }); - const state2 = appReducer(state1, addAction); + const state2 = appReducer(state1, toggleShowFavoritesOnlyAction); expect(state2).toMatchObject({ ...state1, showFavoritesOnly: false }); }); });