mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
Add Native Todo Example Support (#285)
This commit is contained in:
committed by
Aman Varshney
parent
41ec89a79a
commit
906b55599b
5
.changeset/proud-pants-spend.md
Normal file
5
.changeset/proud-pants-spend.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"create-better-t-stack": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Add Native Todo Example Support
|
||||||
@@ -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 (
|
||||||
|
<Container>
|
||||||
|
<ScrollView className="flex-1">
|
||||||
|
<View className="px-4 py-6">
|
||||||
|
<View className="mb-6 rounded-lg border border-border p-4 bg-card">
|
||||||
|
<Text className="text-foreground text-2xl font-bold mb-2">
|
||||||
|
Todo List
|
||||||
|
</Text>
|
||||||
|
<Text className="text-muted-foreground mb-4">
|
||||||
|
Manage your tasks efficiently
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<View className="mb-6">
|
||||||
|
<View className="flex-row items-center space-x-2 mb-2">
|
||||||
|
<TextInput
|
||||||
|
value={newTodoText}
|
||||||
|
onChangeText={setNewTodoText}
|
||||||
|
placeholder="Add a new task..."
|
||||||
|
placeholderTextColor="#6b7280"
|
||||||
|
{{#if (eq backend "convex")}}
|
||||||
|
{{else}}
|
||||||
|
editable={!createMutation.isPending}
|
||||||
|
{{/if}}
|
||||||
|
className="flex-1 border border-border rounded-md px-3 py-2 text-foreground bg-background"
|
||||||
|
onSubmitEditing={handleAddTodo}
|
||||||
|
returnKeyType="done"
|
||||||
|
/>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={handleAddTodo}
|
||||||
|
{{#if (eq backend "convex")}}
|
||||||
|
disabled={!newTodoText.trim()}
|
||||||
|
{{else}}
|
||||||
|
disabled={createMutation.isPending || !newTodoText.trim()}
|
||||||
|
{{/if}}
|
||||||
|
className={`px-4 py-2 rounded-md ${
|
||||||
|
{{#if (eq backend "convex")}}
|
||||||
|
!newTodoText.trim()
|
||||||
|
{{else}}
|
||||||
|
createMutation.isPending || !newTodoText.trim()
|
||||||
|
{{/if}}
|
||||||
|
? "bg-muted"
|
||||||
|
: "bg-primary"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{{#if (eq backend "convex")}}
|
||||||
|
<Text className="text-white font-medium">Add</Text>
|
||||||
|
{{else}}
|
||||||
|
{createMutation.isPending ? (
|
||||||
|
<ActivityIndicator size="small" color="white" />
|
||||||
|
) : (
|
||||||
|
<Text className="text-white font-medium">Add</Text>
|
||||||
|
)}
|
||||||
|
{{/if}}
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{{#if (eq backend "convex")}}
|
||||||
|
{todos === undefined ? (
|
||||||
|
<View className="flex justify-center py-8">
|
||||||
|
<ActivityIndicator size="large" color="#3b82f6" />
|
||||||
|
</View>
|
||||||
|
) : todos.length === 0 ? (
|
||||||
|
<Text className="py-8 text-center text-muted-foreground">
|
||||||
|
No todos yet. Add one above!
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<View className="space-y-2">
|
||||||
|
{todos.map((todo) => (
|
||||||
|
<View
|
||||||
|
key={todo._id}
|
||||||
|
className="flex-row items-center justify-between rounded-md border border-border p-3 bg-background"
|
||||||
|
>
|
||||||
|
<View className="flex-row items-center flex-1">
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() =>
|
||||||
|
handleToggleTodo(todo._id, todo.completed)
|
||||||
|
}
|
||||||
|
className="mr-3"
|
||||||
|
>
|
||||||
|
<Ionicons
|
||||||
|
name={todo.completed ? "checkbox" : "square-outline"}
|
||||||
|
size={24}
|
||||||
|
color={todo.completed ? "#22c55e" : "#6b7280"}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Text
|
||||||
|
className={`flex-1 ${
|
||||||
|
todo.completed
|
||||||
|
? "line-through text-muted-foreground"
|
||||||
|
: "text-foreground"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{todo.text}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => handleDeleteTodo(todo._id)}
|
||||||
|
className="ml-2 p-1"
|
||||||
|
>
|
||||||
|
<Ionicons
|
||||||
|
name="trash-outline"
|
||||||
|
size={20}
|
||||||
|
color="#ef4444"
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{{else}}
|
||||||
|
{todos.isLoading ? (
|
||||||
|
<View className="flex justify-center py-8">
|
||||||
|
<ActivityIndicator size="large" color="#3b82f6" />
|
||||||
|
</View>
|
||||||
|
) : todos.data?.length === 0 ? (
|
||||||
|
<Text className="py-8 text-center text-muted-foreground">
|
||||||
|
No todos yet. Add one above!
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<View className="space-y-2">
|
||||||
|
{todos.data?.map((todo) => (
|
||||||
|
<View
|
||||||
|
key={todo.id}
|
||||||
|
className="flex-row items-center justify-between rounded-md border border-border p-3 bg-background"
|
||||||
|
>
|
||||||
|
<View className="flex-row items-center flex-1">
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() =>
|
||||||
|
handleToggleTodo(todo.id, todo.completed)
|
||||||
|
}
|
||||||
|
className="mr-3"
|
||||||
|
>
|
||||||
|
<Ionicons
|
||||||
|
name={todo.completed ? "checkbox" : "square-outline"}
|
||||||
|
size={24}
|
||||||
|
color={todo.completed ? "#22c55e" : "#6b7280"}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Text
|
||||||
|
className={`flex-1 ${
|
||||||
|
todo.completed
|
||||||
|
? "line-through text-muted-foreground"
|
||||||
|
: "text-foreground"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{todo.text}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => handleDeleteTodo(todo.id)}
|
||||||
|
className="ml-2 p-1"
|
||||||
|
>
|
||||||
|
<Ionicons
|
||||||
|
name="trash-outline"
|
||||||
|
size={20}
|
||||||
|
color="#ef4444"
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{{/if}}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -17,6 +17,18 @@ const DrawerLayout = () => {
|
|||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{{#if (includes examples "todo")}}
|
||||||
|
<Drawer.Screen
|
||||||
|
name="todos"
|
||||||
|
options={{
|
||||||
|
headerTitle: "Todos",
|
||||||
|
drawerLabel: "Todos",
|
||||||
|
drawerIcon: ({ size, color }) => (
|
||||||
|
<Ionicons name="checkbox-outline" size={size} color={color} />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
<Drawer.Screen
|
<Drawer.Screen
|
||||||
name="(tabs)"
|
name="(tabs)"
|
||||||
options={{
|
options={{
|
||||||
Reference in New Issue
Block a user