Skip to content

Commit

Permalink
edit habit
Browse files Browse the repository at this point in the history
  • Loading branch information
owengretzinger committed Dec 22, 2024
1 parent 0b41600 commit ea106fb
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 33 deletions.
2 changes: 1 addition & 1 deletion src/api/habits/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export type HabitWithCompletionsT = z.infer<typeof HabitWithCompletions>;
export const habitCreationSchema = z.object({
title: z.string().min(1, 'Title is required'),
description: z.string().optional(),
color: z.string().min(1, 'Color is required'),
colorName: z.string().min(1, 'Color is required'),
allowMultipleCompletions: z.boolean(),
icon: z.enum(Object.keys(habitIcons) as [keyof typeof habitIcons]),
});
Expand Down
2 changes: 1 addition & 1 deletion src/api/habits/use-create-habit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const useCreateHabit = createMutation<Response, Variables, Error>({
const newHabit: DbHabitT = {
title: variables.habitCreationInfo.title,
description: variables.habitCreationInfo.description,
colorName: variables.habitCreationInfo.color as HabitColorNameT,
colorName: variables.habitCreationInfo.colorName as HabitColorNameT,
icon: variables.habitCreationInfo.icon,
settings: {
allowMultipleCompletions:
Expand Down
52 changes: 52 additions & 0 deletions src/api/habits/use-edit-habit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { showMessage } from 'react-native-flash-message';
import { createMutation } from 'react-query-kit';

import { addTestDelay, queryClient } from '../common';
import { mockHabits, setMockHabits } from './mock-habits';
import { type DbHabitT, type HabitCreationT, type HabitIdT } from './types';

type Response = DbHabitT;
type Variables = {
habitId: HabitIdT;
newHabitInfo: HabitCreationT;
};

export const useEditHabit = createMutation<Response, Variables, Error>({
mutationFn: async ({ habitId, newHabitInfo }) => {
const habitIndex = mockHabits.findIndex((h) => h.id === habitId);
if (habitIndex === -1) throw new Error('Habit not found');

const updatedHabit = {
...mockHabits[habitIndex].data,
...newHabitInfo,
colorName: newHabitInfo.colorName as DbHabitT['colorName'],
settings: {
...mockHabits[habitIndex].data.settings,
allowMultipleCompletions:
newHabitInfo.allowMultipleCompletions ??
mockHabits[habitIndex].data.settings.allowMultipleCompletions,
},
};

const newMockHabits = [...mockHabits];
newMockHabits[habitIndex] = { id: habitId, data: updatedHabit };
setMockHabits(newMockHabits);

return await addTestDelay(updatedHabit);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['habits'] });
showMessage({
message: 'Habit updated successfully',
type: 'success',
duration: 2000,
});
},
onError: () => {
showMessage({
message: 'Failed to update habit',
type: 'danger',
duration: 2000,
});
},
});
45 changes: 31 additions & 14 deletions src/app/habits/edit-habit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import {
habitColorNames,
habitCreationSchema,
type HabitCreationT,
type HabitT,
useCreateHabit,
} from '@/api';
import { useEditHabit } from '@/api/habits/use-edit-habit';
import { HabitIcon, habitIcons } from '@/components/habit-icon';
import {
Button,
Expand All @@ -39,32 +41,44 @@ export default function EditHabit() {
const { colorScheme } = useColorScheme();
const iconModal = useModal();

const { mode } = useLocalSearchParams<{
const { mode, habit: habitJson } = useLocalSearchParams<{
mode: 'edit' | 'create';
id: string;
habit: string;
}>();

const parsedHabit: HabitT = mode === 'edit' ? JSON.parse(habitJson) : null;
const createHabit = useCreateHabit();
const updateHabit = useEditHabit();

const { control, handleSubmit, setValue, watch } = useForm({
const { control, handleSubmit, setValue, watch } = useForm<HabitCreationT>({
resolver: zodResolver(habitCreationSchema),
defaultValues: {
icon: 'diamond' as keyof typeof habitIcons,
title: '',
description: '',
color: 'red',
allowMultipleCompletions: false,
icon: (mode === 'edit'
? parsedHabit.icon
: 'diamond') as keyof typeof habitIcons,
title: mode === 'edit' ? parsedHabit.title : '',
description: mode === 'edit' ? parsedHabit.description : '',
colorName: mode === 'edit' ? parsedHabit.colorName : 'red',
allowMultipleCompletions:
mode === 'edit' ? parsedHabit.settings.allowMultipleCompletions : false,
},
});

const selectedColor = watch('color');
const selectedColor = watch('colorName');
const selectedIcon = watch('icon');
const allowMultipleCompletions = watch('allowMultipleCompletions');

const onSubmit = (data: HabitCreationT) => {
createHabit.mutate({
habitCreationInfo: data,
});
if (mode === 'edit') {
updateHabit.mutate({
habitId: parsedHabit.id,
newHabitInfo: data,
});
} else {
createHabit.mutate({
habitCreationInfo: data,
});
}
router.back();
};

Expand Down Expand Up @@ -125,7 +139,7 @@ export default function EditHabit() {
style={{
width: '11.1%',
}}
onPress={() => setValue('color', col)}
onPress={() => setValue('colorName', col)}
>
<View
className="flex h-full w-full items-center justify-center rounded-full"
Expand Down Expand Up @@ -160,7 +174,10 @@ export default function EditHabit() {
</Switch.Root>
</View>
</View>
<Button label="Create Habit" onPress={handleSubmit(onSubmit)} />
<Button
label={mode === 'edit' ? 'Save Changes' : 'Create Habit'}
onPress={handleSubmit(onSubmit)}
/>
</View>
</ScrollView>

Expand Down
34 changes: 17 additions & 17 deletions src/components/habit-card.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable max-lines-per-function */
import { Link } from 'expo-router';
import { Link, router } from 'expo-router';
import { ActivityIcon, CheckIcon, EllipsisIcon } from 'lucide-react-native';
import { useColorScheme } from 'nativewind';
import React, { useEffect, useState } from 'react';
Expand Down Expand Up @@ -69,11 +69,7 @@ export function HabitCard({ habit }: HabitCardProps) {
colorScheme === 'dark' ? colors.stone.light : habit.color.light,
}}
>
<HabitHeader
habitId={habit.id}
title={habit.title}
icon={habit.icon}
/>
<HabitHeader habit={habit} />
<View className="flex flex-row">
<HabitFriendCompletions habit={habit} />
</View>
Expand All @@ -98,32 +94,36 @@ export function HabitCard({ habit }: HabitCardProps) {
}

interface HabitHeaderProps {
habitId: HabitIdT;
title: string;
icon: string;
habit: HabitT;
}
const HabitHeader = ({ habitId, title, icon }: HabitHeaderProps) => {
const HabitHeader = ({ habit }: HabitHeaderProps) => {
const { colorScheme } = useColorScheme();
const { moveHabit, canMoveHabit } = useHabitOrder();

const menuItems = [
{
key: 'Move up in list',
title: 'Move up in list',
onSelect: () => moveHabit(habitId, 'up'),
disabled: !canMoveHabit(habitId, 'up'),
onSelect: () => moveHabit(habit.id, 'up'),
disabled: !canMoveHabit(habit.id, 'up'),
},
{
key: 'Move down in list',
title: 'Move down in list',
onSelect: () => moveHabit(habitId, 'down'),
disabled: !canMoveHabit(habitId, 'down'),
onSelect: () => moveHabit(habit.id, 'down'),
disabled: !canMoveHabit(habit.id, 'down'),
},
{
key: 'Edit habit',
title: 'Edit habit',
onSelect: () => {
alert('todo');
router.push({
pathname: '/habits/edit-habit',
params: {
mode: 'edit',
habit: JSON.stringify(habit),
},
});
},
},
{
Expand All @@ -139,15 +139,15 @@ const HabitHeader = ({ habitId, title, icon }: HabitHeaderProps) => {
<View className="ml-1 flex-row items-center justify-between">
<View className="mr-2 flex-1 flex-row items-center gap-1">
<HabitIcon
icon={icon as keyof typeof habitIcons}
icon={habit.icon as keyof typeof habitIcons}
size={24}
color={colorScheme === 'dark' ? colors.white : colors.black}
/>
<Text
numberOfLines={1}
className="mb-1 flex-1 text-base font-bold text-black dark:text-white"
>
{title}
{habit.title}
</Text>
</View>

Expand Down

0 comments on commit ea106fb

Please sign in to comment.