Skip to content

Commit

Permalink
Add habits page
Browse files Browse the repository at this point in the history
  • Loading branch information
domhhv committed Feb 12, 2024
1 parent d366709 commit 153464d
Show file tree
Hide file tree
Showing 45 changed files with 1,223 additions and 141 deletions.
3 changes: 2 additions & 1 deletion .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
"runtime": "automatic"
}
]
]
],
"plugins": ["react-hot-loader/babel"]
}
1 change: 0 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
SUPABASE_URL=https://<your_supabase_url>.supabase.co
SUPABASE_ANON_KEY=<your_supabase_anon_key>
SUPABASE_STORAGE_URL=https://[project_id].supabase.co/storage/v1/object/authenticated/[bucket]/[asset-name]
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
"prebuild": "yarn typecheck && yarn eslint:check && yarn prettier:check",
"predeploy": "yarn build",
"deploy": "gh-pages -d dist",
"start": "serve -s dist",
"dev": "NODE_ENV=development webpack serve --mode development --open --hot",
"build": "NODE_ENV=production webpack --mode production",
"dev": "NODE_ENV=development webpack-dev-server --open --hot",
"build": "NODE_ENV=production webpack",
"start": "NODE_ENV=production serve -s dist",
"eslint:check": "eslint src",
"eslint:fix": "eslint --fix src",
"prettier:check": "prettier --check src",
Expand Down Expand Up @@ -90,7 +90,9 @@
"eslint-plugin-unused-imports": "^3.0.0",
"husky": "^8.0.3",
"prettier": "3.1.1",
"react-hot-loader": "^4.13.1",
"style-loader": "^3.3.3",
"supabase": "^1.142.2",
"ts-loader": "^9.5.1",
"typescript": "^5.3.3"
}
Expand Down
29 changes: 24 additions & 5 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ import {
CalendarEventsProvider,
SnackbarProvider,
} from '@context';
import { supabaseClient, theme } from '@helpers';
import { USER_THEME_STORAGE_KEY } from '@hooks';
import { createCalendar, getWeeksInMonth } from '@internationalized/date';
import { CssVarsProvider, styled } from '@mui/joy';
import { SessionContextProvider } from '@supabase/auth-helpers-react';
import { supabaseClient, theme } from '@utils';
import { generateCalendarRange } from '@utils';
import React from 'react';
import { useLocale } from 'react-aria';
import { hot } from 'react-hot-loader/root';
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
import { useCalendarState } from 'react-stately';

import HabitsPage from './habit/HabitsPage';

Expand All @@ -27,24 +32,38 @@ const StyledAppContainerDiv = styled('div')({
});

const App = () => {
const { locale } = useLocale();
const state = useCalendarState({
locale,
createCalendar,
});
const weeksInMonth = getWeeksInMonth(state.visibleRange.start, locale);
const range = generateCalendarRange(state, weeksInMonth);

return (
<CssVarsProvider
theme={theme}
defaultMode="light"
theme={theme}
modeStorageKey={USER_THEME_STORAGE_KEY}
>
<SessionContextProvider supabaseClient={supabaseClient}>
<SnackbarProvider>
<AuthProvider>
<HabitsProvider>
<CalendarEventsProvider>
<CalendarEventsProvider range={range}>
<BrowserRouter>
<AppHeader />
<StyledAppContainerDiv>
<Routes>
<Route
path="/calendar"
element={<Calendar aria-label="Event date" />}
element={
<Calendar
state={state}
weeksInMonth={weeksInMonth}
aria-label="Event date"
/>
}
/>
<Route path="/habits" element={<HabitsPage />} />
<Route path="/account" element={<AccountPage />} />
Expand All @@ -64,4 +83,4 @@ const App = () => {
);
};

export default App;
export default hot(App);
15 changes: 7 additions & 8 deletions src/components/calendar/Calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createCalendar } from '@internationalized/date';
import { capitalizeFirstLetter } from '@utils';
import React from 'react';
import { AriaButtonProps, useCalendar, useLocale } from 'react-aria';
import { useCalendarState } from 'react-stately';
import { type CalendarState, useCalendarState } from 'react-stately';

import CalendarGrid from './CalendarGrid';
import CalendarHeader from './CalendarHeader';
Expand All @@ -11,13 +11,12 @@ import {
StyledCalendarContainerDiv,
} from './styled';

const Calendar = () => {
const { locale } = useLocale();
const state = useCalendarState({
locale,
createCalendar,
});
type CalendarProps = {
state: CalendarState;
weeksInMonth: number;
};

const Calendar = ({ weeksInMonth, state }: CalendarProps) => {
const { calendarProps, prevButtonProps, nextButtonProps, title } =
useCalendar({}, state);

Expand All @@ -41,7 +40,7 @@ const Calendar = () => {
onNavigateBack={state.focusPreviousPage}
onNavigateForward={state.focusNextPage}
/>
<CalendarGrid state={state} />
<CalendarGrid state={state} weeksInMonth={weeksInMonth} />
</StyledCalendarContainerDiv>
</StyledCalendarBackgroundDiv>
);
Expand Down
35 changes: 12 additions & 23 deletions src/components/calendar/CalendarCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
import FmdBadIcon from '@mui/icons-material/FmdBad';
import FmdGoodIcon from '@mui/icons-material/FmdGood';
import { ChipDelete, CircularProgress, Typography } from '@mui/joy';
import { useSession, useUser } from '@supabase/auth-helpers-react';
import { useUser } from '@supabase/auth-helpers-react';
import { getHabitIconUrl } from '@utils';
import React from 'react';

import {
Expand Down Expand Up @@ -34,21 +35,16 @@ const CalendarCell = ({
onClick,
rangeStatus,
}: CalendarCellProps) => {
const session = useSession();
const user = useUser();
const {
removeCalendarEvent,
fetchingCalendarEvents,
calendarEventIdBeingDeleted,
} = useCalendarEvents();
const { habits } = useHabits();
const { habitsMap } = useHabits();
const [active, setActive] = React.useState(false);
const [current, setCurrent] = React.useState(false);

React.useEffect(() => {
console.log({ session });
}, [session]);

React.useEffect(() => {
const today = new Date();
setActive(rangeStatus === 'in-range');
Expand Down Expand Up @@ -104,22 +100,8 @@ const CalendarCell = ({
</StyledCalendarDayCellButtonHeader>
<StyledCalendarDayCellButtonIconsContainer>
{events.map((event) => {
const eventHabit = habits[event.habit_id] || {};
const eventHabit = habitsMap[event.habit_id] || {};
const isGoodHabit = eventHabit.trait === 'good';
const hasIcon = !!eventHabit.icon_path;

const icon = hasIcon ? (
<img
src={`${process.env.SUPABASE_STORAGE_URL}/${user?.id}/${eventHabit.icon_path}`}
alt={`${eventHabit.name} icon`}
width={24}
height={24}
/>
) : isGoodHabit ? (
<FmdGoodIcon fontSize="small" />
) : (
<FmdBadIcon fontSize="small" />
);

const isBeingDeleted = calendarEventIdBeingDeleted === event.id;

Expand All @@ -142,7 +124,14 @@ const CalendarCell = ({
variant="soft"
color={isGoodHabit ? 'success' : 'danger'}
key={event.id}
startDecorator={icon}
startDecorator={
<img
src={getHabitIconUrl(eventHabit.icon_path)}
alt={`${eventHabit.name} icon`}
width={16}
height={16}
/>
}
disabled={isBeingDeleted}
endDecorator={endDecorator}
>
Expand Down
31 changes: 12 additions & 19 deletions src/components/calendar/CalendarGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { useCalendarEvents, useHabits } from '@context';
import { getWeeksInMonth } from '@internationalized/date';
import { Box, Typography } from '@mui/joy';
import { listFiles, StorageBuckets } from '@services';
import { useSupabaseClient, useUser } from '@supabase/auth-helpers-react';
import { getHabitIconUrl } from '@utils';
import { AnimatePresence } from 'framer-motion';
import React from 'react';
import { useCalendarGrid, useLocale } from 'react-aria';
import { useCalendarGrid } from 'react-aria';
import { CalendarState } from 'react-stately';

import MotionCalendarMonthGrid from './CalendarMonthGrid';
Expand All @@ -16,48 +17,40 @@ import {

type CalendarGridProps = {
state: CalendarState;
weeksInMonth: number;
};

const WEEK_DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];

const CalendarGrid = ({ state }: CalendarGridProps) => {
const { locale } = useLocale();
const CalendarGrid = ({ weeksInMonth, state }: CalendarGridProps) => {
const { gridProps } = useCalendarGrid({}, state);
const { habits } = useHabits();
const { habitsMap } = useHabits();
const user = useUser();
const supabase = useSupabaseClient();
// const [habitIcons, setHabitIcons] = React.useState<Record<string, string>>(
// {}
// );

const weeksInMonth = getWeeksInMonth(state.visibleRange.start, locale);

const { calendarEventsByDate } = useCalendarEvents();
const [dayModalDialogOpen, setDayModalDialogOpen] = React.useState(false);
const [activeDate, setActiveDate] = React.useState<Date | null>(null);

React.useEffect(() => {
const loadHabitIcons = async () => {
const { data } = await supabase.storage
.from('habit_icons')
.list(user?.id, {
limit: 100,
offset: 0,
sortBy: { column: 'name', order: 'asc' },
});
const { data } = await listFiles(
StorageBuckets.HABIT_ICONS,
user?.id as string
);

const habitIconsMap = data?.reduce((acc, icon) => {
return {
...acc,
[icon.name]: `${process.env.SUPABASE_STORAGE_URL}/${icon.name}`,
[icon.name]: getHabitIconUrl(icon.name),
};
}, {});

console.log({ habitIconsMap });
};

void loadHabitIcons();
}, [habits, supabase, user?.id]);
}, [habitsMap, supabase, user?.id]);

const handleDayModalDialogOpen = (
dateNumber: number,
Expand Down
7 changes: 4 additions & 3 deletions src/components/calendar/DayHabitModalDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const DayHabitModalDialog = ({
onClose,
date,
}: DayHabitModalDialogProps) => {
const { habits } = useHabits();
const { habitsMap } = useHabits();
const user = useUser();
const { addCalendarEvent, addingCalendarEvent } = useCalendarEvents();
const [selectedBadHabit, setSelectedBadHabit] = React.useState<number | null>(
Expand All @@ -48,6 +48,7 @@ const DayHabitModalDialog = ({

const calendarEvent = {
day: date.toISOString().split('T')[0],
timestamp: +date,
habit_id: selectedBadHabit as number,
time_of_day: selectedTimeOfDay || null,
user_id: user?.id as string,
Expand Down Expand Up @@ -76,7 +77,7 @@ const DayHabitModalDialog = ({
onClose();
};

const hasHabits = Object.keys(habits).length > 0;
const hasHabits = Object.keys(habitsMap).length > 0;

return (
<Modal open={open} onClose={handleClose}>
Expand Down Expand Up @@ -105,7 +106,7 @@ const DayHabitModalDialog = ({
</Option>
)}
{hasHabits &&
Object.values(habits).map((habit) => (
Object.values(habitsMap).map((habit) => (
<Option key={habit.id} value={habit.id} label={habit.name}>
{habit.name}
<Typography level="body-xs">{habit.trait}</Typography>
Expand Down
6 changes: 6 additions & 0 deletions src/components/calendar/styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,17 @@ export const StyledNavigationIconButton = styled(IconButton)(({ theme }) => ({
'& svg': {
color: getLightNeutralColor(theme),
},
'&:hover': {
backgroundColor: theme.palette.neutral[200],
},
},
[theme.getColorSchemeSelector('dark')]: {
'& svg': {
color: getDarkNeutralColor(theme),
},
'&:hover': {
backgroundColor: theme.palette.neutral[900],
},
},
'&:first-of-type': {
marginRight: theme.spacing(1),
Expand Down
48 changes: 48 additions & 0 deletions src/components/controls/ConfirmDialog/ConfirmDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {
Button,
DialogContent,
DialogTitle,
Modal,
ModalClose,
ModalDialog,
Typography,
} from '@mui/joy';
import React from 'react';

type ConfirmDialogProps = {
open: boolean;
heading: string;
onCancel: () => void;
onConfirm: () => void;
children: React.ReactNode;
loading: boolean;
};

const ConfirmDialog = ({
open,
loading,
heading,
onCancel,
onConfirm,
children,
}: ConfirmDialogProps) => {
return (
<Modal open={open} onClose={onCancel}>
<ModalDialog>
<ModalClose />
<DialogTitle>{heading}</DialogTitle>
<DialogContent>
<Typography>{children}</Typography>
</DialogContent>
<Button onClick={onCancel} disabled={loading}>
Cancel
</Button>
<Button onClick={onConfirm} loading={loading}>
Confirm
</Button>
</ModalDialog>
</Modal>
);
};

export default ConfirmDialog;
2 changes: 2 additions & 0 deletions src/components/controls/ConfirmDialog/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './ConfirmDialog';
export { default as ConfirmDialog } from './ConfirmDialog';
2 changes: 1 addition & 1 deletion src/components/controls/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from './FloatingLabelInput';

export * from './FloatingLabelTextarea';
export * from './ConfirmDialog';
Loading

0 comments on commit 153464d

Please sign in to comment.