diff --git a/src/components/Providers.tsx b/src/components/Providers.tsx
index 5429ff1..78d387c 100644
--- a/src/components/Providers.tsx
+++ b/src/components/Providers.tsx
@@ -1,4 +1,4 @@
-import { HabitsProvider, OccurrencesProvider } from '@context';
+import { OccurrencesProvider } from '@context';
import { supabaseClient } from '@helpers';
import { NextUIProvider } from '@nextui-org/react';
import { SessionContextProvider } from '@supabase/auth-helpers-react';
@@ -15,9 +15,7 @@ const LowerProviders = ({ children }: ProviderProps) => {
return (
-
- {children}
-
+ {children}
);
};
diff --git a/src/components/calendar/CalendarHeader.test.tsx b/src/components/calendar/CalendarHeader.test.tsx
index 074c4b0..43d181b 100644
--- a/src/components/calendar/CalendarHeader.test.tsx
+++ b/src/components/calendar/CalendarHeader.test.tsx
@@ -1,4 +1,4 @@
-import { HabitsProvider, OccurrencesProvider } from '@context';
+import { OccurrencesProvider } from '@context';
import { render } from '@testing-library/react';
import React from 'react';
@@ -25,50 +25,42 @@ describe(CalendarHeader.name, () => {
it.skip('should render month and year', () => {
const { getByText } = render(
-
-
-
-
-
+
+
+
);
expect(getByText('January 2022')).toBeInTheDocument();
});
it.skip('should disable previous button', () => {
const { getByRole } = render(
-
-
-
-
-
+
+
+
);
expect(getByRole('navigate-back')).toBeDisabled();
});
it.skip('should disable next button', () => {
const { getByRole } = render(
-
-
-
-
-
+
+
+
);
expect(getByRole('navigate-forward')).toBeDisabled();
});
it.skip('should call onNavigateBack', () => {
const { getByRole } = render(
-
-
-
-
-
+
+
+
);
getByRole('navigate-back').click();
expect(props.onNavigateBack).toHaveBeenCalled();
@@ -76,11 +68,9 @@ describe(CalendarHeader.name, () => {
it.skip('should call onNavigateForward', () => {
const { getByRole } = render(
-
-
-
-
-
+
+
+
);
getByRole('navigate-forward').click();
expect(props.onNavigateForward).toHaveBeenCalled();
diff --git a/src/components/calendar/CalendarHeader.tsx b/src/components/calendar/CalendarHeader.tsx
index fe0d971..4cb4e51 100644
--- a/src/components/calendar/CalendarHeader.tsx
+++ b/src/components/calendar/CalendarHeader.tsx
@@ -1,8 +1,8 @@
-import { useHabits, useOccurrences } from '@context';
+import { useOccurrences } from '@context';
import { useScreenSize } from '@hooks';
import { Select, SelectItem, Button } from '@nextui-org/react';
import { ArrowFatLeft, ArrowFatRight } from '@phosphor-icons/react';
-import { useTraitsStore } from '@stores';
+import { useTraitsStore, useHabitsStore } from '@stores';
import { useUser } from '@supabase/auth-helpers-react';
import React from 'react';
@@ -51,7 +51,7 @@ const CalendarHeader = ({
onNavigateToYear,
onResetFocusedDate,
}: CalendarHeaderProps) => {
- const { habits } = useHabits();
+ const { habits } = useHabitsStore();
const { traits } = useTraitsStore();
const { filteredBy, filterBy } = useOccurrences();
const user = useUser();
diff --git a/src/components/calendar/DayHabitModalDialog.test.tsx b/src/components/calendar/DayHabitModalDialog.test.tsx
index 6d8140a..073af40 100644
--- a/src/components/calendar/DayHabitModalDialog.test.tsx
+++ b/src/components/calendar/DayHabitModalDialog.test.tsx
@@ -1,4 +1,5 @@
-import { useHabits, useOccurrences } from '@context';
+import { useOccurrences } from '@context';
+import { useHabitsStore } from '@stores';
import { useUser } from '@supabase/auth-helpers-react';
import { fireEvent, render, waitFor } from '@testing-library/react';
import { makeTestHabit } from '@tests';
@@ -9,10 +10,13 @@ import DayHabitModalDialog from './DayHabitModalDialog';
jest.mock('@context', () => ({
useOccurrences: jest.fn(),
- useHabits: jest.fn(),
useSnackbar: jest.fn().mockReturnValue({ showSnackbar: jest.fn() }),
}));
+jest.mock('@stores', () => ({
+ useHabitsStore: jest.fn(),
+}));
+
jest.mock('@supabase/auth-helpers-react', () => ({
useUser: jest.fn(),
}));
@@ -36,7 +40,7 @@ describe(DayHabitModalDialog.name, () => {
});
it('should render', () => {
- (useHabits as jest.Mock).mockReturnValue({ habits: [] });
+ (useHabitsStore as unknown as jest.Mock).mockReturnValue({ habits: [] });
(useUser as jest.Mock).mockReturnValue({ id: '1' });
(format as jest.Mock).mockReturnValue('2021-01-01');
(useOccurrences as jest.Mock).mockReturnValue({
@@ -48,7 +52,7 @@ describe(DayHabitModalDialog.name, () => {
});
it('should not render if date is null', () => {
- (useHabits as jest.Mock).mockReturnValue({ habits: [] });
+ (useHabitsStore as unknown as jest.Mock).mockReturnValue({ habits: [] });
(useUser as jest.Mock).mockReturnValue({ id: '1' });
(format as jest.Mock).mockReturnValue('2021-01-01');
(useOccurrences as jest.Mock).mockReturnValue({
@@ -62,7 +66,7 @@ describe(DayHabitModalDialog.name, () => {
});
it('should not render if open is false', () => {
- (useHabits as jest.Mock).mockReturnValue({ habits: [] });
+ (useHabitsStore as unknown as jest.Mock).mockReturnValue({ habits: [] });
(useUser as jest.Mock).mockReturnValue({ id: '1' });
(format as jest.Mock).mockReturnValue('2021-01-01');
(useOccurrences as jest.Mock).mockReturnValue({
@@ -76,7 +80,7 @@ describe(DayHabitModalDialog.name, () => {
});
it('should not render if date is null', () => {
- (useHabits as jest.Mock).mockReturnValue({ habits: [] });
+ (useHabitsStore as unknown as jest.Mock).mockReturnValue({ habits: [] });
(useUser as jest.Mock).mockReturnValue({ id: '1' });
(format as jest.Mock).mockReturnValue('2021-01-01');
(useOccurrences as jest.Mock).mockReturnValue({
@@ -90,7 +94,7 @@ describe(DayHabitModalDialog.name, () => {
});
it('if no habits are available, should show a message', () => {
- (useHabits as jest.Mock).mockReturnValue({ habits: [] });
+ (useHabitsStore as unknown as jest.Mock).mockReturnValue({ habits: [] });
(useUser as jest.Mock).mockReturnValue({ id: '1' });
(format as jest.Mock).mockReturnValue('2021-01-01');
(useOccurrences as jest.Mock).mockReturnValue({
@@ -102,7 +106,7 @@ describe(DayHabitModalDialog.name, () => {
});
it('should render habit options', () => {
- (useHabits as jest.Mock).mockReturnValue({
+ (useHabitsStore as unknown as jest.Mock).mockReturnValue({
habits: [makeTestHabit()],
});
(useUser as jest.Mock).mockReturnValue({ id: '1' });
@@ -116,7 +120,7 @@ describe(DayHabitModalDialog.name, () => {
});
it.skip('should select habit', async () => {
- (useHabits as jest.Mock).mockReturnValue({
+ (useHabitsStore as unknown as jest.Mock).mockReturnValue({
habits: [makeTestHabit({ id: 42 })],
});
(useUser as jest.Mock).mockReturnValue({ id: '1' });
@@ -137,7 +141,7 @@ describe(DayHabitModalDialog.name, () => {
});
it('on close, should call onClose', () => {
- (useHabits as jest.Mock).mockReturnValue({
+ (useHabitsStore as unknown as jest.Mock).mockReturnValue({
habits: [makeTestHabit()],
});
(useUser as jest.Mock).mockReturnValue({ id: '1' });
@@ -152,7 +156,7 @@ describe(DayHabitModalDialog.name, () => {
});
it.skip('on close, should unselect habit', () => {
- (useHabits as jest.Mock).mockReturnValue({
+ (useHabitsStore as unknown as jest.Mock).mockReturnValue({
habits: [makeTestHabit()],
});
(useUser as jest.Mock).mockReturnValue({ id: '1' });
@@ -174,7 +178,7 @@ describe(DayHabitModalDialog.name, () => {
});
it.skip('on submit, should call addOccurrence with proper arguments', () => {
- (useHabits as jest.Mock).mockReturnValue({
+ (useHabitsStore as unknown as jest.Mock).mockReturnValue({
habits: [makeTestHabit()],
});
(useUser as jest.Mock).mockReturnValue({ id: '1' });
diff --git a/src/components/calendar/DayHabitModalDialog.tsx b/src/components/calendar/DayHabitModalDialog.tsx
index 53de40e..21326c0 100644
--- a/src/components/calendar/DayHabitModalDialog.tsx
+++ b/src/components/calendar/DayHabitModalDialog.tsx
@@ -1,4 +1,4 @@
-import { useOccurrences, useHabits } from '@context';
+import { useOccurrences } from '@context';
import {
Button,
Modal,
@@ -9,6 +9,7 @@ import {
Select,
SelectItem,
} from '@nextui-org/react';
+import { useHabitsStore } from '@stores';
import { useUser } from '@supabase/auth-helpers-react';
import { format } from 'date-fns';
import React, { type MouseEventHandler } from 'react';
@@ -24,7 +25,7 @@ const DayHabitModalDialog = ({
onClose,
date,
}: DayHabitModalDialogProps) => {
- const { habits } = useHabits();
+ const { habits } = useHabitsStore();
const user = useUser();
const { addOccurrence, addingOccurrence } = useOccurrences();
const [selectedHabitIds, setSelectedHabitIds] = React.useState([]);
diff --git a/src/components/habit/add-habit/AddHabitDialogButton.test.tsx b/src/components/habit/add-habit/AddHabitDialogButton.test.tsx
index bac08ae..e2d3350 100644
--- a/src/components/habit/add-habit/AddHabitDialogButton.test.tsx
+++ b/src/components/habit/add-habit/AddHabitDialogButton.test.tsx
@@ -1,6 +1,5 @@
-import { useHabits } from '@context';
import { StorageBuckets, uploadFile } from '@services';
-// import { useSnackbarsStore } from '@stores';
+import { useSnackbarsStore, useHabitsStore } from '@stores';
import { useUser } from '@supabase/auth-helpers-react';
import { act, fireEvent, render, waitFor } from '@testing-library/react';
import React from 'react';
@@ -8,15 +7,13 @@ import React from 'react';
import AddHabitDialogButton from './AddHabitDialogButton';
jest.mock('@context', () => ({
- useHabits: jest.fn().mockReturnValue({ updateHabit: jest.fn() }),
useSnackbar: jest.fn().mockReturnValue({ showSnackbar: jest.fn() }),
- useTraits: jest.fn().mockReturnValue({
- traits: [{ id: 1, slug: 'trait-slug', name: 'Trait' }],
- }),
}));
jest.mock('@stores', () => ({
useSnackbarsStore: jest.fn(),
+ useHabitsStore: jest.fn(),
+ useTraitsStore: jest.fn(),
}));
jest.mock('@services', () => ({
@@ -33,7 +30,7 @@ jest.mock('@supabase/auth-helpers-react', () => ({
describe(AddHabitDialogButton.name, () => {
it.skip('should handle data enter and dialog close', () => {
- (useHabits as jest.Mock).mockReturnValue({
+ (useHabitsStore as unknown as jest.Mock).mockReturnValue({
updateHabit: jest.fn(),
fetchingHabits: false,
});
@@ -78,7 +75,7 @@ describe(AddHabitDialogButton.name, () => {
});
it.skip('should not set habit icon if empty file uploaded', () => {
- (useHabits as jest.Mock).mockReturnValue({
+ (useHabitsStore as unknown as jest.Mock).mockReturnValue({
updateHabit: jest.fn(),
fetchingHabits: false,
});
@@ -98,7 +95,9 @@ describe(AddHabitDialogButton.name, () => {
it.skip('should call addHabit on form submit', () => {
const mockAddHabit = jest.fn().mockReturnValue(Promise.resolve({ id: 1 }));
- (useHabits as jest.Mock).mockReturnValue({ addHabit: mockAddHabit });
+ (useHabitsStore as unknown as jest.Mock).mockReturnValue({
+ addHabit: mockAddHabit,
+ });
(useUser as jest.Mock).mockReturnValue({
id: '4c6b7c3b-ec2f-45fb-8c3a-df16f7a4b3aa',
});
@@ -120,20 +119,20 @@ describe(AddHabitDialogButton.name, () => {
.fn()
.mockReturnValue(Promise.resolve({ id: 1234 }));
const mockUpdateHabit = jest.fn().mockReturnValue(Promise.resolve({}));
- // const mockShowSnackbar = jest.fn();
+ const mockShowSnackbar = jest.fn();
(uploadFile as jest.Mock).mockReturnValue(
Promise.resolve({ data: { path: 'icon-path' } })
);
- (useHabits as jest.Mock).mockReturnValue({
+ (useHabitsStore as unknown as jest.Mock).mockReturnValue({
addHabit: mockAddHabit,
updateHabit: mockUpdateHabit,
});
(useUser as jest.Mock).mockReturnValue({
id: 'uuid-42',
});
- // (useSnackbarsStore as jest.Mock).mockReturnValue({
- // showSnackbar: mockShowSnackbar,
- // });
+ (useSnackbarsStore as unknown as jest.Mock).mockReturnValue({
+ showSnackbar: mockShowSnackbar,
+ });
const { getByRole, getByTestId, getByLabelText } = render(
diff --git a/src/components/habit/add-habit/AddHabitDialogButton.tsx b/src/components/habit/add-habit/AddHabitDialogButton.tsx
index 50e3512..85e642c 100644
--- a/src/components/habit/add-habit/AddHabitDialogButton.tsx
+++ b/src/components/habit/add-habit/AddHabitDialogButton.tsx
@@ -1,5 +1,4 @@
import { AddCustomTraitModal, VisuallyHiddenInput } from '@components';
-import { useHabits } from '@context';
import { useTextField, useFileField } from '@hooks';
import {
Button,
@@ -14,14 +13,14 @@ import {
Textarea,
} from '@nextui-org/react';
import { CloudArrowUp, Plus } from '@phosphor-icons/react';
-import { useTraitsStore } from '@stores';
+import { useHabitsStore, useTraitsStore } from '@stores';
import { useUser } from '@supabase/auth-helpers-react';
import React from 'react';
const AddHabitDialogButton = () => {
const user = useUser();
const { traits } = useTraitsStore();
- const { fetchingHabits, addingHabit, addHabit } = useHabits();
+ const { fetchingHabits, addingHabit, addHabit } = useHabitsStore();
const [open, setOpen] = React.useState(false);
const [name, handleNameChange, clearName] = useTextField();
const [description, handleDescriptionChange, clearDescription] =
diff --git a/src/components/habit/edit-habit/EditHabitDialog.test.tsx b/src/components/habit/edit-habit/EditHabitDialog.test.tsx
index a45deb3..6077d01 100644
--- a/src/components/habit/edit-habit/EditHabitDialog.test.tsx
+++ b/src/components/habit/edit-habit/EditHabitDialog.test.tsx
@@ -1,7 +1,6 @@
-import { useHabits } from '@context';
-// import { useTraitsStore } from '@stores';
+import { useHabitsStore, useTraitsStore } from '@stores';
import { act, fireEvent, render, waitFor } from '@testing-library/react';
-// import { makeTestTrait } from '@tests';
+import { makeTestTrait } from '@tests';
import React from 'react';
import EditHabitDialog, { type EditHabitDialogProps } from './EditHabitDialog';
@@ -18,8 +17,12 @@ jest.mock('@hooks', () => ({
useFileField: jest.fn().mockReturnValue([null, jest.fn(), jest.fn()]),
}));
-jest.mock('@context', () => ({
- useHabits: jest.fn().mockReturnValue({ updateHabit: jest.fn() }),
+jest.mock('@stores', () => ({
+ useHabitsStore: jest.fn().mockReturnValue({
+ updateHabit: jest.fn(),
+ habitIdBeingUpdated: null,
+ }),
+ useTraitsStore: jest.fn().mockReturnValue({ traits: [] }),
}));
jest.mock('@supabase/auth-helpers-react', () => ({
@@ -86,10 +89,12 @@ describe(EditHabitDialog.name, () => {
it.skip('should call updateHabit when submitted', async () => {
const mockUpdateHabit = jest.fn();
- (useHabits as jest.Mock).mockReturnValue({ updateHabit: mockUpdateHabit });
- // (useTraitsStore as jest.Mock).mockReturnValue({
- // traits: [makeTestTrait()],
- // });
+ (useHabitsStore as unknown as jest.Mock).mockReturnValue({
+ updateHabit: mockUpdateHabit,
+ });
+ (useTraitsStore as unknown as jest.Mock).mockReturnValue({
+ traits: [makeTestTrait()],
+ });
const { getByRole, getByLabelText } = render(
);
diff --git a/src/components/habit/edit-habit/EditHabitDialog.tsx b/src/components/habit/edit-habit/EditHabitDialog.tsx
index 4853975..179c17f 100644
--- a/src/components/habit/edit-habit/EditHabitDialog.tsx
+++ b/src/components/habit/edit-habit/EditHabitDialog.tsx
@@ -1,4 +1,3 @@
-import { useHabits } from '@context';
import { useTextField } from '@hooks';
import type { Habit } from '@models';
import {
@@ -13,7 +12,7 @@ import {
SelectItem,
Textarea,
} from '@nextui-org/react';
-import { useTraitsStore } from '@stores';
+import { useHabitsStore, useTraitsStore } from '@stores';
import { useUser } from '@supabase/auth-helpers-react';
import { toEventLike } from '@utils';
import React from 'react';
@@ -33,7 +32,7 @@ const EditHabitDialog = ({
const [name, handleNameChange] = useTextField();
const [description, handleDescriptionChange] = useTextField();
const [traitId, setTraitId] = React.useState('');
- const { updateHabit, habitIdBeingUpdated } = useHabits();
+ const { updateHabit, habitIdBeingUpdated } = useHabitsStore();
const { traits } = useTraitsStore();
const user = useUser();
diff --git a/src/components/habit/habits-page/HabitIconCell.tsx b/src/components/habit/habits-page/HabitIconCell.tsx
index 01d55d7..b50ed9c 100644
--- a/src/components/habit/habits-page/HabitIconCell.tsx
+++ b/src/components/habit/habits-page/HabitIconCell.tsx
@@ -1,7 +1,7 @@
import { VisuallyHiddenInput } from '@components';
-import { useHabits } from '@context';
import { type Habit } from '@models';
import { Button, Tooltip } from '@nextui-org/react';
+import { useHabitsStore } from '@stores';
import { useUser } from '@supabase/auth-helpers-react';
import { getHabitIconUrl } from '@utils';
import React from 'react';
@@ -11,7 +11,7 @@ type HabitIconCellProps = {
};
const HabitIconCell = ({ habit }: HabitIconCellProps) => {
- const { updateHabit } = useHabits();
+ const { updateHabit } = useHabitsStore();
const user = useUser();
const iconUrl = getHabitIconUrl(habit.iconPath);
diff --git a/src/components/habit/habits-page/HabitsPage.test.tsx b/src/components/habit/habits-page/HabitsPage.test.tsx
index 3471ae2..cd588ef 100644
--- a/src/components/habit/habits-page/HabitsPage.test.tsx
+++ b/src/components/habit/habits-page/HabitsPage.test.tsx
@@ -1,4 +1,4 @@
-import { HabitsProvider, useHabits } from '@context';
+import { useHabitsStore } from '@stores';
import { fireEvent, render, waitFor } from '@testing-library/react';
import { makeTestHabit } from '@tests';
import React from 'react';
@@ -6,12 +6,6 @@ import React from 'react';
import HabitsPage from './HabitsPage';
jest.mock('@context', () => ({
- HabitsProvider: jest.fn(({ children }) => children),
- useTraits: jest.fn().mockReturnValue({
- traits: [],
- }),
- useHabits: jest.fn(),
- useSnackbar: jest.fn().mockReturnValue({}),
useOccurrences: jest.fn().mockReturnValue({
removeOccurrencesByHabitId: jest.fn(),
allOccurrences: [],
@@ -28,6 +22,13 @@ jest.mock('@services', () => ({
getHabitTotalEntries: jest.fn().mockResolvedValue(0),
}));
+jest.mock('@stores', () => ({
+ useHabitsStore: jest.fn(),
+ useTraitsStore: jest.fn().mockReturnValue({
+ traits: [],
+ }),
+}));
+
jest.mock('@hooks', () => ({
ThemeMode: {
LIGHT: 'light',
@@ -43,7 +44,7 @@ jest.mock('@hooks', () => ({
describe(HabitsPage.name, () => {
it('should display habits', async () => {
- (useHabits as jest.Mock).mockImplementation(() => ({
+ (useHabitsStore as unknown as jest.Mock).mockImplementation(() => ({
habits: [
makeTestHabit({
name: 'Habit name #1',
@@ -55,11 +56,7 @@ describe(HabitsPage.name, () => {
}),
],
}));
- const { getByText } = render(
-
-
-
- );
+ const { getByText } = render();
await waitFor(() => {
expect(getByText('Your habits'));
expect(getByText('Habit name #1'));
@@ -70,7 +67,7 @@ describe(HabitsPage.name, () => {
});
it('should open edit dialog on edit icon button click', async () => {
- (useHabits as jest.Mock).mockImplementation(() => ({
+ (useHabitsStore as unknown as jest.Mock).mockImplementation(() => ({
habits: [
makeTestHabit({
id: 42,
@@ -83,22 +80,14 @@ describe(HabitsPage.name, () => {
}),
],
}));
- const { queryByRole, getByRole, getByTestId } = render(
-
-
-
- );
+ const { queryByRole, getByRole, getByTestId } = render();
expect(queryByRole('submit-edited-habit-button')).toBeNull();
fireEvent.click(getByTestId('edit-habit-id-42-button'));
expect(getByRole('submit-edited-habit-button')).toBeDefined();
});
it.skip('should open confirm dialog on remove icon button click', async () => {
- const { getByRole, getByTestId } = render(
-
-
-
- );
+ const { getByRole, getByTestId } = render();
fireEvent.click(getByTestId('delete-habit-id-2-button'));
expect(getByRole('dialog')).toBeDefined();
fireEvent.click(getByRole('confirm-dialog-cancel'));
@@ -106,7 +95,7 @@ describe(HabitsPage.name, () => {
it.skip('should remove habit on confirm', async () => {
const mockRemoveHabit = jest.fn();
- (useHabits as jest.Mock).mockImplementation(() => ({
+ (useHabitsStore as unknown as jest.Mock).mockImplementation(() => ({
habits: [
makeTestHabit({
name: 'Habit name #1',
@@ -119,11 +108,7 @@ describe(HabitsPage.name, () => {
],
removeHabit: mockRemoveHabit,
}));
- const { queryByRole, getByRole, getByTestId } = render(
-
-
-
- );
+ const { queryByRole, getByRole, getByTestId } = render();
expect(queryByRole('dialog')).toBeNull();
fireEvent.click(getByTestId('delete-habit-id-2-button'));
expect(getByRole('dialog')).toBeDefined();
diff --git a/src/components/habit/habits-page/HabitsPage.tsx b/src/components/habit/habits-page/HabitsPage.tsx
index 5701fc4..a468986 100644
--- a/src/components/habit/habits-page/HabitsPage.tsx
+++ b/src/components/habit/habits-page/HabitsPage.tsx
@@ -3,7 +3,7 @@ import {
ConfirmDialog,
EditHabitDialog,
} from '@components';
-import { useHabits, useOccurrences } from '@context';
+import { useOccurrences } from '@context';
import { useDocumentTitle } from '@hooks';
import { type Habit } from '@models';
import {
@@ -18,6 +18,7 @@ import {
Tooltip,
} from '@nextui-org/react';
import { PencilSimple, TrashSimple } from '@phosphor-icons/react';
+import { useHabitsStore } from '@stores';
import { useUser } from '@supabase/auth-helpers-react';
import { format } from 'date-fns';
import React from 'react';
@@ -64,7 +65,7 @@ const habitColumns = [
const HabitsPage = () => {
const user = useUser();
- const { habits, removeHabit, habitIdBeingDeleted } = useHabits();
+ const { habits, removeHabit, habitIdBeingDeleted } = useHabitsStore();
const { removeOccurrencesByHabitId } = useOccurrences();
const [habitToEdit, setHabitToEdit] = React.useState(null);
const [habitToRemove, setHabitToRemove] = React.useState(null);
diff --git a/src/context/Habits/HabitsContext.ts b/src/context/Habits/HabitsContext.ts
deleted file mode 100644
index 2cd6181..0000000
--- a/src/context/Habits/HabitsContext.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import type { Habit, HabitsInsert, HabitsUpdate } from '@models';
-import React from 'react';
-
-type HabitsContextType = {
- habitIdBeingUpdated: number | null;
- habitIdBeingDeleted: number | null;
- addingHabit: boolean;
- fetchingHabits: boolean;
- habits: Habit[];
- addHabit: (habit: HabitsInsert, icon?: File | null) => Promise;
- removeHabit: (habit: Habit) => Promise;
- updateHabit: (
- habitId: number,
- userId: string,
- habit: HabitsUpdate,
- icon?: File | null
- ) => Promise;
-};
-
-export const HabitsContext = React.createContext(
- null
-);
-
-export const useHabits = () => {
- const context = React.useContext(HabitsContext);
-
- if (!context) {
- throw new Error('useHabits must be used within a HabitsProvider');
- }
-
- return context;
-};
diff --git a/src/context/Habits/HabitsProvider.tsx b/src/context/Habits/HabitsProvider.tsx
deleted file mode 100644
index 9fbd2c7..0000000
--- a/src/context/Habits/HabitsProvider.tsx
+++ /dev/null
@@ -1,209 +0,0 @@
-import { HabitsContext } from '@context';
-import { useDataFetch } from '@hooks';
-import type { Habit, HabitsInsert, HabitsUpdate } from '@models';
-import {
- createHabit,
- deleteFile,
- destroyHabit,
- listHabits,
- patchHabit,
- StorageBuckets,
- uploadFile,
-} from '@services';
-import { useSnackbarsStore } from '@stores';
-import { makeTestHabit } from '@tests';
-import { getErrorMessage } from '@utils';
-import React, { type ReactNode } from 'react';
-
-const HabitsProvider = ({ children }: { children: ReactNode }) => {
- const { showSnackbar } = useSnackbarsStore();
- const [addingHabit, setAddingHabit] = React.useState(false);
- const [fetchingHabits, setFetchingHabits] = React.useState(false);
- const [habits, setHabits] = React.useState([makeTestHabit()]);
- const [habitIdBeingUpdated, setHabitIdBeingUpdated] = React.useState<
- number | null
- >(null);
- const [habitIdBeingDeleted, setHabitIdBeingDeleted] = React.useState<
- number | null
- >(null);
-
- const fetchHabits = React.useCallback(async () => {
- try {
- setFetchingHabits(true);
- setHabits(await listHabits());
- } catch (error) {
- console.error(error);
- showSnackbar(
- 'Something went wrong while fetching your habits. Please try reloading the page.',
- {
- description: `Error details: ${getErrorMessage(error)}`,
- color: 'danger',
- dismissible: true,
- }
- );
- } finally {
- setFetchingHabits(false);
- }
- }, [showSnackbar]);
-
- const clearHabits = React.useCallback(() => {
- setHabits([]);
- }, []);
-
- useDataFetch({
- clear: clearHabits,
- load: fetchHabits,
- });
-
- const uploadHabitIcon = async (userId: string, icon?: File | null) => {
- let iconPath = '';
-
- if (icon) {
- iconPath = `${userId}/${Date.now()}-${icon.name}`;
- await uploadFile(StorageBuckets.HABIT_ICONS, iconPath, icon);
- }
-
- return iconPath;
- };
-
- const addHabit = React.useCallback(
- async (habit: HabitsInsert, icon?: File | null) => {
- try {
- setAddingHabit(true);
-
- const iconPath = await uploadHabitIcon(habit.userId, icon);
-
- const newHabit = await createHabit({ ...habit, iconPath });
-
- setHabits((prevHabits) => [...prevHabits, newHabit]);
-
- showSnackbar('Your habit has been added!', {
- color: 'success',
- dismissible: true,
- dismissText: 'Done',
- });
- } catch (error) {
- showSnackbar(
- 'Something went wrong while adding your habit. Please try again.',
- {
- description: `Error details: ${getErrorMessage(error)}`,
- color: 'danger',
- dismissible: true,
- }
- );
-
- console.error(error);
- } finally {
- setAddingHabit(false);
- }
- },
- [showSnackbar]
- );
-
- const updateHabit = React.useCallback(
- async (
- id: number,
- userId: string,
- habit: HabitsUpdate,
- icon?: File | null
- ) => {
- try {
- setHabitIdBeingUpdated(id);
-
- const iconPath = await uploadHabitIcon(userId, icon);
-
- const updatedHabit = await patchHabit(id, { ...habit, iconPath });
-
- setHabits((prevHabits) => {
- const habitIndex = prevHabits.findIndex((h) => h.id === id);
- const nextHabits = [...prevHabits];
- nextHabits[habitIndex] = updatedHabit;
- return nextHabits;
- });
-
- showSnackbar('Your habit has been updated!', {
- color: 'success',
- dismissible: true,
- });
- } catch (error) {
- showSnackbar(
- 'Something went wrong while updating your habit. Please try again.',
- {
- description: `Error details: ${getErrorMessage(error)}`,
- color: 'danger',
- dismissible: true,
- }
- );
-
- console.error(error);
- } finally {
- setHabitIdBeingUpdated(null);
- }
- },
- [showSnackbar]
- );
-
- const removeHabit = React.useCallback(
- async ({ id, iconPath }: Habit) => {
- try {
- setHabitIdBeingDeleted(id);
-
- await destroyHabit(id);
-
- if (iconPath) {
- await deleteFile(StorageBuckets.HABIT_ICONS, iconPath);
- }
-
- setHabits((prevHabits) => {
- return prevHabits.filter((habit) => habit.id !== id);
- });
-
- showSnackbar('Your habit has been deleted.', {
- dismissible: true,
- });
- } catch (error) {
- showSnackbar(
- 'Something went wrong while deleting your habit. Please try again.',
- {
- description: `Error details: ${getErrorMessage(error)}`,
- color: 'danger',
- dismissible: true,
- }
- );
-
- console.error(error);
- } finally {
- setHabitIdBeingDeleted(null);
- }
- },
- [showSnackbar]
- );
-
- const value = React.useMemo(() => {
- return {
- habitIdBeingUpdated,
- habitIdBeingDeleted,
- addingHabit,
- fetchingHabits,
- habits,
- addHabit,
- removeHabit,
- updateHabit,
- };
- }, [
- habitIdBeingUpdated,
- habitIdBeingDeleted,
- addingHabit,
- fetchingHabits,
- habits,
- addHabit,
- removeHabit,
- updateHabit,
- ]);
-
- return (
- {children}
- );
-};
-
-export default React.memo(HabitsProvider);
diff --git a/src/context/Habits/index.ts b/src/context/Habits/index.ts
deleted file mode 100644
index a82d71b..0000000
--- a/src/context/Habits/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export * from './HabitsContext';
-export * from './HabitsProvider';
-export { default as HabitsProvider } from './HabitsProvider';
diff --git a/src/context/Occurrences/OccurrencesProvider.tsx b/src/context/Occurrences/OccurrencesProvider.tsx
index 00e9778..f003cd9 100644
--- a/src/context/Occurrences/OccurrencesProvider.tsx
+++ b/src/context/Occurrences/OccurrencesProvider.tsx
@@ -1,4 +1,4 @@
-import { OccurrencesContext, useHabits } from '@context';
+import { OccurrencesContext } from '@context';
import { cacheOccurrence, occurrencesCache, uncacheOccurrence } from '@helpers';
import { useDataFetch } from '@hooks';
import type {
@@ -11,7 +11,7 @@ import {
destroyOccurrence,
listOccurrences,
} from '@services';
-import { useSnackbarsStore, useTraitsStore } from '@stores';
+import { useHabitsStore, useSnackbarsStore, useTraitsStore } from '@stores';
import { getErrorMessage } from '@utils';
import React, { type ReactNode } from 'react';
@@ -26,7 +26,7 @@ export type OccurrenceFilters = {
const OccurrencesProvider = ({ children }: Props) => {
const { showSnackbar } = useSnackbarsStore();
- const { habits } = useHabits();
+ const { habits } = useHabitsStore();
const { traits } = useTraitsStore();
const [addingOccurrence, setAddingOccurrence] = React.useState(false);
@@ -69,7 +69,7 @@ const OccurrencesProvider = ({ children }: Props) => {
}, [range, showSnackbar]);
const clearOccurrences = React.useCallback(() => {
- setOccurrences([]);
+ setAllOccurrences([]);
occurrencesCache.clear();
}, []);
@@ -197,7 +197,7 @@ const OccurrencesProvider = ({ children }: Props) => {
);
const removeOccurrencesByHabitId = (habitId: number) => {
- setOccurrences((prevOccurrences) => {
+ setAllOccurrences((prevOccurrences) => {
return prevOccurrences.filter((occurrence) => {
return occurrence.habitId !== habitId;
});
diff --git a/src/context/index.ts b/src/context/index.ts
index e080738..40ff0a0 100644
--- a/src/context/index.ts
+++ b/src/context/index.ts
@@ -1,3 +1 @@
-export * from './Habits';
-
export * from './Occurrences';
diff --git a/src/index.tsx b/src/index.tsx
index e5bd876..327f931 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -9,7 +9,7 @@ const root = ReactDOM.createRoot(
);
root.render(
- //
-
- //
+
+
+
);
diff --git a/src/stores/habits.store.ts b/src/stores/habits.store.ts
new file mode 100644
index 0000000..6368dce
--- /dev/null
+++ b/src/stores/habits.store.ts
@@ -0,0 +1,171 @@
+import type { Habit, HabitsInsert, HabitsUpdate } from '@models';
+import {
+ createHabit,
+ deleteFile,
+ destroyHabit,
+ listHabits,
+ patchHabit,
+ StorageBuckets,
+ uploadFile,
+} from '@services';
+import { makeTestHabit } from '@tests';
+import { getErrorMessage } from '@utils';
+import { create } from 'zustand';
+
+import { useSnackbarsStore } from './index';
+
+const { showSnackbar } = useSnackbarsStore.getState();
+
+type HabitsState = {
+ habits: Habit[];
+ addingHabit: boolean;
+ fetchingHabits: boolean;
+ habitIdBeingUpdated: number | null;
+ habitIdBeingDeleted: number | null;
+ fetchHabits: () => Promise;
+ addHabit: (habit: HabitsInsert, icon?: File | null) => Promise;
+ updateHabit: (
+ id: number,
+ userId: string,
+ habit: HabitsUpdate,
+ icon?: File | null
+ ) => Promise;
+ removeHabit: (habit: Habit) => Promise;
+};
+
+const useHabitsStore = create((set) => ({
+ habits: [makeTestHabit()],
+ addingHabit: false,
+ fetchingHabits: false,
+ habitIdBeingUpdated: null,
+ habitIdBeingDeleted: null,
+
+ fetchHabits: async () => {
+ set({ fetchingHabits: true });
+
+ try {
+ const habits = await listHabits();
+ set({ habits });
+ } catch (error) {
+ console.error(error);
+
+ showSnackbar(
+ 'Something went wrong while fetching your habits. Please try reloading the page.',
+ {
+ description: `Error details: ${getErrorMessage(error)}`,
+ color: 'danger',
+ dismissible: true,
+ }
+ );
+ } finally {
+ set({ fetchingHabits: false });
+ }
+ },
+
+ addHabit: async (habit: HabitsInsert, icon?: File | null) => {
+ set({ addingHabit: true });
+
+ try {
+ const iconPath = await uploadHabitIcon(habit.userId, icon);
+ const newHabit = await createHabit({ ...habit, iconPath });
+ set((state) => ({ habits: [...state.habits, newHabit] }));
+
+ showSnackbar('Your habit has been added!', {
+ color: 'success',
+ dismissible: true,
+ dismissText: 'Done',
+ });
+ } catch (error) {
+ console.error(error);
+
+ showSnackbar(
+ 'Something went wrong while adding your habit. Please try again.',
+ {
+ description: `Error details: ${getErrorMessage(error)}`,
+ color: 'danger',
+ dismissible: true,
+ }
+ );
+ } finally {
+ set({ addingHabit: false });
+ }
+ },
+
+ updateHabit: async (
+ id: number,
+ userId: string,
+ habit: HabitsUpdate,
+ icon?: File | null
+ ) => {
+ set({ habitIdBeingUpdated: id });
+
+ try {
+ const iconPath = await uploadHabitIcon(userId, icon);
+ const updatedHabit = await patchHabit(id, { ...habit, iconPath });
+ set((state) => ({
+ habits: state.habits.map((h) => (h.id === id ? updatedHabit : h)),
+ }));
+ showSnackbar('Your habit has been updated!', {
+ color: 'success',
+ dismissible: true,
+ });
+ } catch (error) {
+ console.error(error);
+
+ showSnackbar(
+ 'Something went wrong while updating your habit. Please try again.',
+ {
+ description: `Error details: ${getErrorMessage(error)}`,
+ color: 'danger',
+ dismissible: true,
+ }
+ );
+ } finally {
+ set({ habitIdBeingUpdated: null });
+ }
+ },
+
+ removeHabit: async ({ id, iconPath }: Habit) => {
+ set({ habitIdBeingDeleted: id });
+
+ try {
+ await destroyHabit(id);
+
+ if (iconPath) {
+ await deleteFile(StorageBuckets.HABIT_ICONS, iconPath);
+ }
+
+ set((state) => ({
+ habits: state.habits.filter((habit) => habit.id !== id),
+ }));
+
+ showSnackbar('Your habit has been deleted.', {
+ dismissible: true,
+ });
+ } catch (error) {
+ console.error(error);
+
+ showSnackbar(
+ 'Something went wrong while deleting your habit. Please try again.',
+ {
+ description: `Error details: ${getErrorMessage(error)}`,
+ color: 'danger',
+ dismissible: true,
+ }
+ );
+ } finally {
+ set({ habitIdBeingDeleted: null });
+ }
+ },
+}));
+
+const uploadHabitIcon = async (userId: string, icon?: File | null) => {
+ let iconPath = '';
+ if (icon) {
+ iconPath = `${userId}/${Date.now()}-${icon.name}`;
+ await uploadFile(StorageBuckets.HABIT_ICONS, iconPath, icon);
+ }
+ return iconPath;
+};
+
+export default useHabitsStore;
diff --git a/src/stores/index.ts b/src/stores/index.ts
index 4683d72..3f85349 100644
--- a/src/stores/index.ts
+++ b/src/stores/index.ts
@@ -1,2 +1,3 @@
export { default as useSnackbarsStore } from './snackbars.store';
export { default as useTraitsStore } from './traits.store';
+export { default as useHabitsStore } from './habits.store';
diff --git a/src/stores/traits.store.ts b/src/stores/traits.store.ts
index fb70da2..53627f1 100644
--- a/src/stores/traits.store.ts
+++ b/src/stores/traits.store.ts
@@ -19,12 +19,13 @@ const testTraits = [
makeTestTrait({ name: 'Test Bad Trait', color: '#F6F6F6' }),
];
+const { showSnackbar } = useSnackbarsStore.getState();
+
const useTraitsStore = create((set) => ({
traits: testTraits,
fetchingTraits: false,
addingTrait: false,
fetchTraits: async () => {
- const { showSnackbar } = useSnackbarsStore.getState();
try {
set({ fetchingTraits: true });
const traits = await listTraits();
@@ -44,7 +45,6 @@ const useTraitsStore = create((set) => ({
}
},
addTrait: async (trait: TraitsInsert) => {
- const { showSnackbar } = useSnackbarsStore.getState();
try {
set({ addingTrait: true });
const newTrait = await createTrait(trait);