diff --git a/apps/mobile/app/(app)/(tabs)/settings.tsx b/apps/mobile/app/(app)/(tabs)/settings.tsx index 9915f4ad..6c926b33 100644 --- a/apps/mobile/app/(app)/(tabs)/settings.tsx +++ b/apps/mobile/app/(app)/(tabs)/settings.tsx @@ -91,7 +91,7 @@ export default function SettingsScreen() { } /> - + ( - + @@ -107,12 +107,19 @@ export default function AuthenticatedLayout() { }} /> + ) } diff --git a/apps/mobile/app/(app)/category/[categoryId].tsx b/apps/mobile/app/(app)/category/[categoryId].tsx new file mode 100644 index 00000000..d74f4ff4 --- /dev/null +++ b/apps/mobile/app/(app)/category/[categoryId].tsx @@ -0,0 +1,54 @@ +import { CategoryForm } from '@/components/category/category-form' +import { Text } from '@/components/ui/text' +import { updateCategory } from '@/mutations/category' +import { categoryQueries, useCategories } from '@/queries/category' +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { useLocalSearchParams, useRouter } from 'expo-router' +import { Alert, ScrollView, View } from 'react-native' + +export default function EditCategoryScreen() { + const { categoryId } = useLocalSearchParams<{ categoryId: string }>() + const { data: categories = [] } = useCategories() + const queryClient = useQueryClient() + const router = useRouter() + + const { mutateAsync: mutateUpdate } = useMutation({ + mutationFn: updateCategory, + onError(error) { + Alert.alert(error.message) + }, + onSuccess() { + router.back() + }, + async onSettled() { + await queryClient.invalidateQueries({ + queryKey: categoryQueries.list._def, + }) + }, + throwOnError: true, + }) + + const category = categories.find((category) => category.id === categoryId) + + if (!category) { + return ( + + Category not found + + ) + } + + return ( + + mutateUpdate({ id: category.id, data: values })} + hiddenFields={['type']} + defaultValues={{ + name: category?.name, + icon: category?.icon!, + type: category?.type, + }} + /> + + ) +} diff --git a/apps/mobile/app/(app)/categories/index.tsx b/apps/mobile/app/(app)/category/index.tsx similarity index 97% rename from apps/mobile/app/(app)/categories/index.tsx rename to apps/mobile/app/(app)/category/index.tsx index 37dfacb0..e0bc5cb0 100644 --- a/apps/mobile/app/(app)/categories/index.tsx +++ b/apps/mobile/app/(app)/category/index.tsx @@ -54,7 +54,7 @@ export default function CategoriesScreen() { label={t(i18n)`New ${section.key.toLowerCase()}`} onPress={() => router.push({ - pathname: '/categories/new-category', + pathname: '/category/new-category', params: { type: section.key }, }) } diff --git a/apps/mobile/app/(app)/categories/new-category.tsx b/apps/mobile/app/(app)/category/new-category.tsx similarity index 91% rename from apps/mobile/app/(app)/categories/new-category.tsx rename to apps/mobile/app/(app)/category/new-category.tsx index 514f07e6..5287d117 100644 --- a/apps/mobile/app/(app)/categories/new-category.tsx +++ b/apps/mobile/app/(app)/category/new-category.tsx @@ -8,7 +8,9 @@ import { Alert, View } from 'react-native' export default function CreateCategoryScreen() { const router = useRouter() - const { type } = useLocalSearchParams<{ type?: CategoryTypeType }>() + const { type = 'EXPENSE' } = useLocalSearchParams<{ + type?: CategoryTypeType + }>() const queryClient = useQueryClient() const { mutateAsync } = useMutation({ mutationFn: createCategory, diff --git a/apps/mobile/components/category/category-form.tsx b/apps/mobile/components/category/category-form.tsx index 04f2602e..bd1c5776 100644 --- a/apps/mobile/components/category/category-form.tsx +++ b/apps/mobile/components/category/category-form.tsx @@ -15,11 +15,13 @@ import { SelectCategoryIconField } from './select-category-icon-field' type CategoryFormProps = { onSubmit: (data: CategoryFormValues) => void defaultValues?: Partial + hiddenFields?: Array<'type'> } export const CategoryForm = ({ onSubmit, defaultValues, + hiddenFields = [], }: CategoryFormProps) => { const { i18n } = useLingui() const nameInputRef = useRef(null) @@ -28,11 +30,14 @@ export const CategoryForm = ({ resolver: zodResolver(zCategoryFormValues), defaultValues: { name: '', - type: 'EXPENSE', icon: 'CreditCard', ...defaultValues, + type: defaultValues?.type || 'EXPENSE', }, }) + const type = categoryForm.watch('type') + + const isTypeHidden = hiddenFields.includes('type') return ( @@ -45,34 +50,47 @@ export const CategoryForm = ({ autoCapitalize="none" autoFocus={!defaultValues} className="!pl-[62px]" + disabled={categoryForm.formState.isLoading} leftSection={ nameInputRef.current?.focus()} /> } /> - {t(i18n)`Type`} - ( - - - - {t(i18n)`Expense`} - - - {t(i18n)`Income`} - - - - )} - /> + {!isTypeHidden && ( + <> + {t(i18n)`Type`} + ( + + + + {t(i18n)`Expense`} + + + {t(i18n)`Income`} + + + + )} + /> + + )} = ({ category }) => { asChild push href={{ - pathname: '/categories/[categoryId]', + pathname: '/category/[categoryId]', params: { categoryId: category.id }, }} > diff --git a/apps/mobile/components/category/select-category-icon-field.tsx b/apps/mobile/components/category/select-category-icon-field.tsx index 80e59533..652aa817 100644 --- a/apps/mobile/components/category/select-category-icon-field.tsx +++ b/apps/mobile/components/category/select-category-icon-field.tsx @@ -1,17 +1,27 @@ import { BottomSheetBackdrop, BottomSheetModal } from '@gorhom/bottom-sheet' -import { useRef } from 'react' +import { useMemo, useRef } from 'react' -import { WALLET_ICONS } from '@/lib/icons/wallet-icons' +import { + CATEGORY_EXPENSE_ICONS, + CATEGORY_INCOME_ICONS, +} from '@/lib/icons/category-icons' +import { sleep } from '@/lib/utils' +import type { CategoryTypeType } from '@6pm/validation' import { useController } from 'react-hook-form' import { Keyboard } from 'react-native' +import { FullWindowOverlay } from 'react-native-screens' import GenericIcon from '../common/generic-icon' import { IconGridSheet } from '../common/icon-grid-sheet' import { Button } from '../ui/button' export function SelectCategoryIconField({ onSelect, + disabled, + type, }: { onSelect?: (currency: string) => void + disabled?: boolean + type: CategoryTypeType }) { const sheetRef = useRef(null) const { @@ -19,10 +29,16 @@ export function SelectCategoryIconField({ // fieldState, } = useController({ name: 'icon' }) + const icons = useMemo( + () => (type === 'EXPENSE' ? CATEGORY_EXPENSE_ICONS : CATEGORY_INCOME_ICONS), + [type], + ) + return ( <>