Skip to content

Commit

Permalink
feat(calendar): use url params for visible months and weeks state man…
Browse files Browse the repository at this point in the history
…agement (#149)

* feat(calendar): use url params for visible months and weeks state management

* fix: tests
  • Loading branch information
domhhv authored Jan 3, 2025
1 parent cd34b04 commit e5c072c
Show file tree
Hide file tree
Showing 28 changed files with 291 additions and 359 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ This app showcases the use of the following tools and technologies:
## Roadmap

- [x] **Dark Mode**: Switch between light and dark themes.
- [ ] **Weekly View**: View your habits on a weekly calendar.
- [ ] **Daily View**: Dive into your habits on a daily calendar.
- [ ] **Export**: Export your habits and entries.
- [ ] **Environments**: Associate habits with environments where they occur.
- [ ] **Categories**: Group habits into categories.
- [ ] **Weekly View**: View your habits on a weekly calendar.
- [ ] **Daily View**: Dive into your habits on a daily calendar.
- [ ] **Sharing**: Share your calendar with trusted people.
- [ ] **Statistics**: Track your progress with insightful statistics.
- [ ] **Notifications**: Get reminders to log your habits.
Expand Down
10 changes: 8 additions & 2 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,14 @@ const App = () => {
<AppHeader />
<main className="flex h-full flex-1 flex-col items-start bg-background">
<Routes>
<Route path="/calendar/month" element={<MonthCalendar />} />
<Route path="/calendar/week" element={<WeekCalendar />} />
<Route
path="/calendar/month/:year?/:month?/:day?"
element={<MonthCalendar />}
/>
<Route
path="/calendar/week/:year?/:month?/:day?"
element={<WeekCalendar />}
/>
<Route path="/habits" element={<HabitsPage />} />
<Route path="/account" element={<AccountPage />} />
<Route path="*" element={<Navigate to="/calendar/month" replace />} />
Expand Down
24 changes: 16 additions & 8 deletions src/components/Providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@ type ProviderProps = {
children: ReactNode;
};

const LowerProviders = ({ children }: ProviderProps) => {
const LowerProviders = React.memo(function WrappedProvider({
children,
}: ProviderProps) {
const navigate = useNavigate();

return <NextUIProvider navigate={navigate}>{children}</NextUIProvider>;
};
});

const PotentialSupabaseProvider = ({ children }: ProviderProps) => {
const PotentialSupabaseProvider = React.memo(function WrappedProvider({
children,
}: ProviderProps) {
if (!Object.keys(supabaseClient).length) {
return children;
}
Expand All @@ -25,24 +29,28 @@ const PotentialSupabaseProvider = ({ children }: ProviderProps) => {
{children}
</SessionContextProvider>
);
};
});

const UpperProviders = ({ children }: ProviderProps) => {
const UpperProviders = React.memo(function WrappedProvider({
children,
}: ProviderProps) {
return (
<BrowserRouter>
<I18nProvider locale="en-GB">{children}</I18nProvider>
</BrowserRouter>
);
};
});

const Providers = ({ children }: ProviderProps) => {
const Providers = React.memo(function WrappedProviders({
children,
}: ProviderProps) {
return (
<UpperProviders>
<PotentialSupabaseProvider>
<LowerProviders>{children}</LowerProviders>
</PotentialSupabaseProvider>
</UpperProviders>
);
};
});

export default Providers;
91 changes: 29 additions & 62 deletions src/components/calendar-month/MonthCalendar.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { generateCalendarRange } from '@helpers';
import { useDocumentTitle } from '@hooks';
import { CalendarDate, GregorianCalendar } from '@internationalized/date';
import { useDisclosure } from '@nextui-org/react';
import { useOccurrencesStore } from '@stores';
import { capitalizeFirstLetter } from '@utils';
import clsx from 'clsx';
import { endOfMonth, endOfWeek, startOfMonth, startOfWeek } from 'date-fns';
import React from 'react';
import { type AriaButtonProps, useCalendar, useLocale } from 'react-aria';
import { useLocation } from 'react-router-dom';
import { useCalendar, useLocale } from 'react-aria';
import { useParams } from 'react-router-dom';
import { useCalendarState } from 'react-stately';
import { useShallow } from 'zustand/react/shallow';

import MonthCalendarGrid from './MonthCalendarGrid';
import MonthCalendarHeader from './MonthCalendarHeader';
Expand All @@ -25,14 +26,15 @@ const createCalendar = (identifier: string) => {
};

const MonthCalendar = () => {
const { onRangeChange } = useOccurrencesStore();
const onRangeChange = useOccurrencesStore(
useShallow((state) => state.onRangeChange)
);
const { locale } = useLocale();
const calendarState = useCalendarState({
locale,
createCalendar,
});
const { calendarProps, prevButtonProps, nextButtonProps, title } =
useCalendar({}, calendarState);
const { calendarProps, title } = useCalendar({}, calendarState);
const {
isOpen: isNoteDialogOpen,
onOpen: openNoteDialog,
Expand All @@ -44,63 +46,42 @@ const MonthCalendar = () => {
onClose: closeOccurrenceDialog,
} = useDisclosure();
const [activeDate, setActiveDate] = React.useState<Date | null>(null);
const { state: locationState } = useLocation();

const setFocusedDate = React.useCallback(
(year: number, month: number, day: number) => {
const nextFocusedDate = new CalendarDate(year, month, day);
calendarState.setFocusedDate(nextFocusedDate);
},
[calendarState]
);
const params = useParams();

React.useEffect(() => {
if (!locationState) {
const { year, month, day } = params;

if (!year || !month || !day) {
return;
}

const { year, month } = locationState;
const focusedDate = new Date(Number(year), Number(month) - 1, 1);

setFocusedDate(year, month, 1);
}, [locationState, setFocusedDate]);
const rangeStart = startOfWeek(startOfMonth(focusedDate));
const rangeEnd = endOfWeek(endOfMonth(focusedDate));

React.useEffect(() => {
onRangeChange(
generateCalendarRange(
calendarState.visibleRange.start.year,
calendarState.visibleRange.start.month
)
onRangeChange([+rangeStart, +rangeEnd]);

const nextFocusedDate = new CalendarDate(
focusedDate.getFullYear(),
focusedDate.getMonth() + 1,
focusedDate.getDate()
);
}, [
calendarState.visibleRange.start.year,
calendarState.visibleRange.start.month,
onRangeChange,
]);

const hasFocusedDateChanged =
calendarState.focusedDate.toString() !== nextFocusedDate.toString();

if (hasFocusedDateChanged) {
calendarState.setFocusedDate(nextFocusedDate);
}
}, [params, calendarState, onRangeChange]);

const [activeMonthLabel, activeYear] = title.split(' ');

useDocumentTitle(
`${activeMonthLabel.slice(0, 3)} ${activeYear} | Habitrack Calendar`
);

const navigateToMonth = (month: number) => {
const { year, day } = calendarState.focusedDate;
setFocusedDate(year, month, day);
};

const navigateToYear = (year: number) => {
const { month, day } = calendarState.focusedDate;
setFocusedDate(year, month, day);
};

const resetFocusedDate = () => {
const now = new Date();
const day = now.getDate();
const month = now.getMonth();
const year = now.getFullYear();
setFocusedDate(year, month + 1, day);
};

const handleOccurrenceModalClose = () => {
window.setTimeout(() => {
closeOccurrenceDialog();
Expand Down Expand Up @@ -133,13 +114,6 @@ const MonthCalendar = () => {
setActiveDate(new Date(fullYear, monthIndex - 1, dateNumber, 12));
};

const transformButtonProps = (
buttonProps: Pick<AriaButtonProps<'button'>, 'isDisabled' | 'aria-label'>
) => ({
'aria-label': buttonProps['aria-label'] || '',
disabled: Boolean(buttonProps.isDisabled),
});

const calendarContainerClassName = clsx(
'flex h-full w-full max-w-full flex-1 flex-col gap-2 p-0 px-8 pb-8 lg:gap-4 lg:px-16 lg:py-4',
isOccurrenceDialogOpen && 'pointer-events-none'
Expand All @@ -151,13 +125,6 @@ const MonthCalendar = () => {
<MonthCalendarHeader
activeMonthLabel={capitalizeFirstLetter(activeMonthLabel)}
activeYear={activeYear}
prevButtonProps={transformButtonProps(prevButtonProps)}
nextButtonProps={transformButtonProps(nextButtonProps)}
onNavigateBack={calendarState.focusPreviousPage}
onNavigateForward={calendarState.focusNextPage}
onNavigateToMonth={navigateToMonth}
onNavigateToYear={navigateToYear}
onResetFocusedDate={resetFocusedDate}
/>
<MonthCalendarGrid
activeMonthLabel={capitalizeFirstLetter(activeMonthLabel)}
Expand Down
Loading

0 comments on commit e5c072c

Please sign in to comment.