diff --git a/.changeset/proud-pants-spend.md b/.changeset/proud-pants-spend.md new file mode 100644 index 0000000..bdf5601 --- /dev/null +++ b/.changeset/proud-pants-spend.md @@ -0,0 +1,5 @@ +--- +"create-better-t-stack": patch +--- + +Add Native Todo Example Support diff --git a/apps/cli/templates/examples/todo/native/nativewind/app/todo.tsx.hbs b/apps/cli/templates/examples/todo/native/nativewind/app/todo.tsx.hbs new file mode 100644 index 0000000..efc13f8 --- /dev/null +++ b/apps/cli/templates/examples/todo/native/nativewind/app/todo.tsx.hbs @@ -0,0 +1,295 @@ +import { useState } from "react"; +import { + View, + Text, + TextInput, + TouchableOpacity, + ScrollView, + ActivityIndicator, + Alert, +} from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +{{#if (eq backend "convex")}} +import { useMutation, useQuery } from "convex/react"; +import { api } from "@{{projectName}}/backend/convex/_generated/api.js"; +import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel.d.ts"; +{{else}} +import { useMutation, useQuery } from "@tanstack/react-query"; +{{/if}} + +import { Container } from "@/components/container"; +{{#unless (eq backend "convex")}} +{{#if (eq api "orpc")}} +import { orpc } from "@/utils/orpc"; +{{/if}} +{{#if (eq api "trpc")}} +import { trpc } from "@/utils/trpc"; +{{/if}} +{{/unless}} + +export default function TodosScreen() { + const [newTodoText, setNewTodoText] = useState(""); + + {{#if (eq backend "convex")}} + const todos = useQuery(api.todos.getAll); + const createTodoMutation = useMutation(api.todos.create); + const toggleTodoMutation = useMutation(api.todos.toggle); + const deleteTodoMutation = useMutation(api.todos.deleteTodo); + + const handleAddTodo = async () => { + const text = newTodoText.trim(); + if (!text) return; + await createTodoMutation({ text }); + setNewTodoText(""); + }; + + const handleToggleTodo = (id: Id<"todos">, currentCompleted: boolean) => { + toggleTodoMutation({ id, completed: !currentCompleted }); + }; + + const handleDeleteTodo = (id: Id<"todos">) => { + Alert.alert("Delete Todo", "Are you sure you want to delete this todo?", [ + { text: "Cancel", style: "cancel" }, + { + text: "Delete", + style: "destructive", + onPress: () => deleteTodoMutation({ id }), + }, + ]); + }; + {{else}} + {{#if (eq api "orpc")}} + const todos = useQuery(orpc.todo.getAll.queryOptions()); + const createMutation = useMutation( + orpc.todo.create.mutationOptions({ + onSuccess: () => { + todos.refetch(); + setNewTodoText(""); + }, + }), + ); + const toggleMutation = useMutation( + orpc.todo.toggle.mutationOptions({ + onSuccess: () => todos.refetch(), + }), + ); + const deleteMutation = useMutation( + orpc.todo.delete.mutationOptions({ + onSuccess: () => todos.refetch(), + }), + ); + {{/if}} + {{#if (eq api "trpc")}} + const todos = useQuery(trpc.todo.getAll.queryOptions()); + const createMutation = useMutation( + trpc.todo.create.mutationOptions({ + onSuccess: () => { + todos.refetch(); + setNewTodoText(""); + }, + }), + ); + const toggleMutation = useMutation( + trpc.todo.toggle.mutationOptions({ + onSuccess: () => todos.refetch(), + }), + ); + const deleteMutation = useMutation( + trpc.todo.delete.mutationOptions({ + onSuccess: () => todos.refetch(), + }), + ); + {{/if}} + + const handleAddTodo = () => { + if (newTodoText.trim()) { + createMutation.mutate({ text: newTodoText }); + } + }; + + const handleToggleTodo = (id: number, completed: boolean) => { + toggleMutation.mutate({ id, completed: !completed }); + }; + + const handleDeleteTodo = (id: number) => { + Alert.alert("Delete Todo", "Are you sure you want to delete this todo?", [ + { text: "Cancel", style: "cancel" }, + { + text: "Delete", + style: "destructive", + onPress: () => deleteMutation.mutate({ id }), + }, + ]); + }; + {{/if}} + + return ( + + + + + + Todo List + + + Manage your tasks efficiently + + + + + + + {{#if (eq backend "convex")}} + Add + {{else}} + {createMutation.isPending ? ( + + ) : ( + Add + )} + {{/if}} + + + + + {{#if (eq backend "convex")}} + {todos === undefined ? ( + + + + ) : todos.length === 0 ? ( + + No todos yet. Add one above! + + ) : ( + + {todos.map((todo) => ( + + + + handleToggleTodo(todo._id, todo.completed) + } + className="mr-3" + > + + + + {todo.text} + + + handleDeleteTodo(todo._id)} + className="ml-2 p-1" + > + + + + ))} + + )} + {{else}} + {todos.isLoading ? ( + + + + ) : todos.data?.length === 0 ? ( + + No todos yet. Add one above! + + ) : ( + + {todos.data?.map((todo) => ( + + + + handleToggleTodo(todo.id, todo.completed) + } + className="mr-3" + > + + + + {todo.text} + + + handleDeleteTodo(todo.id)} + className="ml-2 p-1" + > + + + + ))} + + )} + {{/if}} + + + + + ); +} diff --git a/apps/cli/templates/frontend/native/nativewind/app/(drawer)/_layout.tsx b/apps/cli/templates/frontend/native/nativewind/app/(drawer)/_layout.tsx.hbs similarity index 74% rename from apps/cli/templates/frontend/native/nativewind/app/(drawer)/_layout.tsx rename to apps/cli/templates/frontend/native/nativewind/app/(drawer)/_layout.tsx.hbs index c1b5640..ca5f6c9 100644 --- a/apps/cli/templates/frontend/native/nativewind/app/(drawer)/_layout.tsx +++ b/apps/cli/templates/frontend/native/nativewind/app/(drawer)/_layout.tsx.hbs @@ -17,6 +17,18 @@ const DrawerLayout = () => { ), }} /> + {{#if (includes examples "todo")}} + ( + + ), + }} + /> + {{/if}}