add ai and todo example templates for native frontends (#293)

This commit is contained in:
Aman Varshney
2025-06-02 16:30:53 +05:30
committed by GitHub
parent 9dbeea8983
commit 7851d0636d
42 changed files with 1606 additions and 536 deletions

View File

@@ -0,0 +1,340 @@
import { useState } from "react";
import {
View,
Text,
TextInput,
TouchableOpacity,
ScrollView,
ActivityIndicator,
Alert,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
{{#if (eq backend "convex")}}
import { useMutation, useQuery } from "convex/react";
import { api } from "@{{projectName}}/backend/convex/_generated/api";
import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel";
{{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("");
const { theme } = useUnistyles();
{{#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}}
const isLoading = {{#if (eq backend "convex")}}!todos{{else}}todos.isLoading{{/if}};
const isCreating = {{#if (eq backend "convex")}}false{{else}}createMutation.isPending{{/if}};
const primaryButtonTextColor = theme.colors.background;
return (
<Container>
<ScrollView style={styles.scrollView}>
<View style={styles.headerContainer}>
<Text style={styles.headerTitle}>Todo List</Text>
<Text style={styles.headerSubtitle}>
Manage your tasks efficiently
</Text>
<View style={styles.inputContainer}>
<TextInput
value={newTodoText}
onChangeText={setNewTodoText}
placeholder="Add a new task..."
placeholderTextColor={theme.colors.border}
editable={!isCreating}
style={styles.textInput}
onSubmitEditing={handleAddTodo}
returnKeyType="done"
/>
<TouchableOpacity
onPress={handleAddTodo}
disabled={isCreating || !newTodoText.trim()}
style={[
styles.addButton,
(isCreating || !newTodoText.trim()) && styles.addButtonDisabled,
]}
>
{isCreating ? (
<ActivityIndicator size="small" color={primaryButtonTextColor} />
) : (
<Ionicons
name="add"
size={24}
color={primaryButtonTextColor}
/>
)}
</TouchableOpacity>
</View>
</View>
{isLoading && (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={theme.colors.primary} />
<Text style={styles.loadingText}>Loading todos...</Text>
</View>
)}
{{#if (eq backend "convex")}}
{todos && todos.length === 0 && !isLoading && (
<Text style={styles.emptyText}>No todos yet. Add one!</Text>
)}
{todos?.map((todo) => (
<View key={todo._id} style={styles.todoItem}>
<TouchableOpacity
onPress={() => handleToggleTodo(todo._id, todo.completed)}
style={styles.todoContent}
>
<Ionicons
name={todo.completed ? "checkbox" : "square-outline"}
size={24}
color={todo.completed ? theme.colors.primary : theme.colors.typography}
style={styles.checkbox}
/>
<Text
style={[
styles.todoText,
todo.completed && styles.todoTextCompleted,
]}
>
{todo.text}
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => handleDeleteTodo(todo._id)}>
<Ionicons name="trash-outline" size={24} color={theme.colors.destructive} />
</TouchableOpacity>
</View>
))}
{{else}}
{todos.data && todos.data.length === 0 && !isLoading && (
<Text style={styles.emptyText}>No todos yet. Add one!</Text>
)}
{todos.data?.map((todo: { id: number; text: string; completed: boolean }) => (
<View key={todo.id} style={styles.todoItem}>
<TouchableOpacity
onPress={() => handleToggleTodo(todo.id, todo.completed)}
style={styles.todoContent}
>
<Ionicons
name={todo.completed ? "checkbox" : "square-outline"}
size={24}
color={todo.completed ? theme.colors.primary : theme.colors.typography}
style={styles.checkbox}
/>
<Text
style={[
styles.todoText,
todo.completed && styles.todoTextCompleted,
]}
>
{todo.text}
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => handleDeleteTodo(todo.id)}>
<Ionicons name="trash-outline" size={24} color={theme.colors.destructive} />
</TouchableOpacity>
</View>
))}
{{/if}}
</ScrollView>
</Container>
);
}
const styles = StyleSheet.create((theme) => ({
scrollView: {
flex: 1,
},
headerContainer: {
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.lg,
borderBottomWidth: 1,
borderBottomColor: theme.colors.border,
backgroundColor: theme.colors.background,
},
headerTitle: {
fontSize: 28,
fontWeight: "bold",
color: theme.colors.typography,
marginBottom: theme.spacing.sm,
},
headerSubtitle: {
fontSize: 16,
color: theme.colors.typography,
marginBottom: theme.spacing.md,
},
inputContainer: {
flexDirection: "row",
alignItems: "center",
marginBottom: theme.spacing.md,
},
textInput: {
flex: 1,
borderWidth: 1,
borderColor: theme.colors.border,
borderRadius: 8,
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
color: theme.colors.typography,
backgroundColor: theme.colors.background,
marginRight: theme.spacing.sm,
fontSize: 16,
},
addButton: {
backgroundColor: theme.colors.primary,
padding: theme.spacing.sm,
borderRadius: 8,
justifyContent: "center",
alignItems: "center",
},
addButtonDisabled: {
backgroundColor: theme.colors.border,
},
loadingContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
padding: theme.spacing.lg,
},
loadingText: {
marginTop: theme.spacing.sm,
fontSize: 16,
color: theme.colors.typography,
},
emptyText: {
textAlign: "center",
marginTop: theme.spacing.xl,
fontSize: 16,
color: theme.colors.typography,
},
todoItem: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingVertical: theme.spacing.md,
paddingHorizontal: theme.spacing.md,
borderBottomWidth: 1,
borderBottomColor: theme.colors.border,
backgroundColor: theme.colors.background,
},
todoContent: {
flexDirection: "row",
alignItems: "center",
flex: 1,
},
checkbox: {
marginRight: theme.spacing.md,
},
todoText: {
fontSize: 16,
color: theme.colors.typography,
flex: 1,
},
todoTextCompleted: {
textDecorationLine: "line-through",
color: theme.colors.border,
},
}));