-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(mobile): update categories to use zustand store
- Loading branch information
Showing
10 changed files
with
323 additions
and
73 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,27 @@ | ||
import { CategoryForm } from '@/components/category/category-form' | ||
import { createCategory } from '@/mutations/category' | ||
import { categoryQueries } from '@/queries/category' | ||
import type { CategoryTypeType } from '@6pm/validation' | ||
import { useMutation, useQueryClient } from '@tanstack/react-query' | ||
import { useCreateCategory } from '@/stores/category/hooks' | ||
import type { CategoryFormValues, CategoryTypeType } from '@6pm/validation' | ||
import { createId } from '@paralleldrive/cuid2' | ||
import { useLocalSearchParams, useRouter } from 'expo-router' | ||
import { Alert, View } from 'react-native' | ||
import { View } from 'react-native' | ||
|
||
export default function CreateCategoryScreen() { | ||
const router = useRouter() | ||
const { type = 'EXPENSE' } = useLocalSearchParams<{ | ||
type?: CategoryTypeType | ||
}>() | ||
const queryClient = useQueryClient() | ||
const { mutateAsync } = useMutation({ | ||
mutationFn: createCategory, | ||
onError(error) { | ||
Alert.alert(error.message) | ||
}, | ||
onSuccess() { | ||
router.back() | ||
}, | ||
async onSettled() { | ||
await queryClient.invalidateQueries({ | ||
queryKey: categoryQueries.list._def, | ||
}) | ||
}, | ||
}) | ||
const { mutateAsync } = useCreateCategory() | ||
|
||
const handleCreate = async (data: CategoryFormValues) => { | ||
mutateAsync({ data, id: createId() }).catch(() => { | ||
// ignore | ||
}) | ||
router.back() | ||
} | ||
|
||
return ( | ||
<View className="py-3 px-6 bg-card h-screen"> | ||
<CategoryForm onSubmit={mutateAsync} defaultValues={{ type }} /> | ||
<CategoryForm onSubmit={handleCreate} defaultValues={{ type }} /> | ||
</View> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,30 @@ | ||
const { getDefaultConfig } = require('expo/metro-config'); | ||
const { withNativeWind } = require('nativewind/metro'); | ||
const { getDefaultConfig } = require("expo/metro-config"); | ||
const { withNativeWind } = require("nativewind/metro"); | ||
|
||
const path = require('path'); | ||
const path = require("path"); | ||
|
||
// Find the project and workspace directories | ||
const projectRoot = __dirname; | ||
// This can be replaced with `find-yarn-workspace-root` | ||
const monorepoRoot = path.resolve(projectRoot, '../..'); | ||
const monorepoRoot = path.resolve(projectRoot, "../.."); | ||
|
||
const config = getDefaultConfig(projectRoot); | ||
|
||
// 1. Watch all files within the monorepo | ||
config.watchFolders = [monorepoRoot]; | ||
// 2. Let Metro know where to resolve packages and in what order | ||
config.resolver.nodeModulesPaths = [ | ||
path.resolve(projectRoot, 'node_modules'), | ||
path.resolve(monorepoRoot, 'node_modules'), | ||
path.resolve(projectRoot, "node_modules"), | ||
path.resolve(monorepoRoot, "node_modules"), | ||
]; | ||
|
||
config.resolver.unstable_conditionNames = [ | ||
"browser", | ||
"require", | ||
"react-native", | ||
]; | ||
|
||
config.resolver.unstable_enableSymlinks = true; | ||
config.resolver.unstable_enablePackageExports = true; | ||
|
||
module.exports = withNativeWind(config, { input: './global.css' }); | ||
module.exports = withNativeWind(config, { input: "./global.css" }); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
import { getHonoClient } from '@/lib/client' | ||
import { useMeQuery } from '@/queries/auth' | ||
import { | ||
type Category, | ||
type CategoryFormValues, | ||
CategorySchema, | ||
} from '@6pm/validation' | ||
import { createId } from '@paralleldrive/cuid2' | ||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' | ||
import { keyBy, omit } from 'lodash-es' | ||
import { useMemo } from 'react' | ||
import { z } from 'zod' | ||
import { categoryQueries } from './queries' | ||
import { useCategoryStore } from './store' | ||
|
||
export const useCategoryList = () => { | ||
const categories = useCategoryStore().categories | ||
const setCategoriesState = useCategoryStore((state) => state.setCategories) | ||
|
||
const query = useQuery({ | ||
...categoryQueries.all({ setCategoriesState }), | ||
initialData: categories, | ||
}) | ||
|
||
const { categoriesDict, incomeCategories, expenseCategories } = | ||
useMemo(() => { | ||
const categoriesDict = keyBy(categories, 'id') | ||
const incomeCategories = categories.filter( | ||
(category) => category.type === 'INCOME', | ||
) | ||
const expenseCategories = categories.filter( | ||
(category) => category.type === 'EXPENSE', | ||
) | ||
|
||
return { | ||
categoriesDict, | ||
incomeCategories, | ||
expenseCategories, | ||
} | ||
}, [categories]) | ||
|
||
return { | ||
...query, | ||
categories, | ||
categoriesDict, | ||
incomeCategories, | ||
expenseCategories, | ||
} | ||
} | ||
|
||
export const useCategory = (categoryId: string) => { | ||
const categories = useCategoryStore().categories | ||
const category: Category | null = useMemo( | ||
() => categories.find((category) => category.id === categoryId) || null, | ||
[categories, categoryId], | ||
) | ||
|
||
return { category } | ||
} | ||
|
||
export const useUpdateCategory = () => { | ||
const updateCategoryInStore = useCategoryStore( | ||
(state) => state.updateCategory, | ||
) | ||
const { categoriesDict } = useCategoryList() | ||
const queryClient = useQueryClient() | ||
|
||
const mutation = useMutation( | ||
{ | ||
mutationFn: async ({ | ||
id, | ||
data, | ||
}: { id: string; data: CategoryFormValues }) => { | ||
const hc = await getHonoClient() | ||
const result = await hc.v1.categories[':categoryId'].$put({ | ||
param: { categoryId: id }, | ||
json: omit(data, 'type'), // prevent updating category type | ||
}) | ||
|
||
if (result.ok) { | ||
const category = CategorySchema.parse(await result.json()) | ||
return category | ||
} | ||
|
||
throw result | ||
}, | ||
onMutate({ id, data }) { | ||
let category = categoriesDict[id] | ||
if (!category) { | ||
return | ||
} | ||
|
||
category = { ...category, ...data, updatedAt: new Date() } | ||
|
||
updateCategoryInStore(category) | ||
|
||
return category | ||
}, | ||
}, | ||
queryClient, | ||
) | ||
|
||
return mutation | ||
} | ||
|
||
export const useCreateCategory = () => { | ||
const { data: userData } = useMeQuery() | ||
const updateCategoryInStore = useCategoryStore( | ||
(state) => state.updateCategory, | ||
) | ||
|
||
const mutation = useMutation({ | ||
mutationFn: async ({ | ||
id = createId(), | ||
data, | ||
}: { id?: string; data: CategoryFormValues }) => { | ||
const hc = await getHonoClient() | ||
const result = await hc.v1.categories.$post({ | ||
json: { id, ...data }, | ||
}) | ||
|
||
if (result.ok) { | ||
const json = await result.json() | ||
const category = CategorySchema.extend({ | ||
id: z.string(), | ||
}).parse(json) | ||
return category | ||
} | ||
|
||
throw result | ||
}, | ||
onMutate({ id, data }) { | ||
const category: Category = { | ||
id: id!, | ||
createdAt: new Date(), | ||
updatedAt: new Date(), | ||
parentId: null, | ||
userId: userData?.id || '', | ||
description: '', | ||
color: '', | ||
icon: '', | ||
...data, | ||
} | ||
|
||
updateCategoryInStore(category) | ||
|
||
return category | ||
}, | ||
}) | ||
|
||
return mutation | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { getHonoClient } from '@/lib/client' | ||
import { type Category, CategorySchema } from '@6pm/validation' | ||
import { createQueryKeys } from '@lukemorales/query-key-factory' | ||
|
||
export const categoryQueries = createQueryKeys('categories', { | ||
all: ({ | ||
setCategoriesState, | ||
}: { setCategoriesState: (categories: Category[]) => void }) => ({ | ||
queryKey: [{}], | ||
queryFn: async () => { | ||
const hc = await getHonoClient() | ||
const res = await hc.v1.categories.$get() | ||
if (!res.ok) { | ||
throw new Error(await res.text()) | ||
} | ||
|
||
const items = await res.json() | ||
const categories = items.map((item) => CategorySchema.parse(item)) | ||
|
||
setCategoriesState(categories) | ||
|
||
return categories | ||
}, | ||
}), | ||
}) |
Oops, something went wrong.