Skip to content

Commit

Permalink
Init to win it.
Browse files Browse the repository at this point in the history
  • Loading branch information
saimon24 committed Sep 18, 2023
1 parent 89099fb commit 48633b4
Show file tree
Hide file tree
Showing 36 changed files with 1,119 additions and 453 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,12 @@ yarn-error.*

# typescript
*.tsbuildinfo

# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb
# The following patterns were generated by expo-cli

expo-env.d.ts
# @end expo-cli

.env
.gitignroe/
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"deno.enable": false,
"deno.lint": false,
"deno.unstable": false
}
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# AI Shopping List with React Native & Supabase

This is a React Native app that uses Supabase as a backend. It is a simple shopping list app that allows you to add items to a list and check them off when you buy them.

It's using the PGVector extension to store embeddings in Supabase and then compare new items to the embeddings of items in the database to find the closest match.

## Supabase

Find the migration and edge function in the `supabase` folder.

## App Screenshots

<div style="display: flex; flex-direction: 'row';">
<img src="./screenshots/1.png" width=30%>
<img src="./screenshots/2.png" width=30%>
<img src="./screenshots/3.png" width=30%>
</div>
33 changes: 33 additions & 0 deletions app/(app)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import { Stack } from 'expo-router';
import { TouchableOpacity } from 'react-native';
import { useAuth } from '../../provider/AuthProvider';
import { Ionicons } from '@expo/vector-icons';

const Layout = () => {
const { signOut, session } = useAuth();
return (
<Stack
screenOptions={{
headerStyle: {
backgroundColor: '#151515',
},
headerTintColor: '#fff',
}}>
<Stack.Screen
name="index"
redirect={!session}
options={{
title: 'SupaList',
headerRight: () => (
<TouchableOpacity onPress={signOut}>
<Ionicons name="log-out-outline" size={24} color="white" />
</TouchableOpacity>
),
}}
/>
</Stack>
);
};

export default Layout;
140 changes: 140 additions & 0 deletions app/(app)/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { View, Text, ListRenderItem, StyleSheet, SectionList } from 'react-native';
import React, { useEffect, useState } from 'react';
import { supabase } from '../../config/initSupabase';
import { TouchableOpacity } from 'react-native-gesture-handler';
import { useAuth } from '../../provider/AuthProvider';
import { Ionicons } from '@expo/vector-icons';
import BottomGrocerySheet from '../../components/BottomGrocerySheet';

export default function TabOneScreen() {
const [listItems, setListItems] = useState<any[]>([]);
const { user } = useAuth();
// Define list categories
const [groceryOptions, setGroceryOptions] = useState<any[]>([
'Banana',
'Apple',
'Oats',
'Milk',
'Eggs',
'Bread',
'Butter',
]);

useEffect(() => {
const fetchData = async () => {
// Load categories from Supabase
let { data: categories } = await supabase.from('categories').select('id, category');
// Load products from Supabase
const { data: products } = await supabase.from('products').select().eq('historic', false);
const { data: historic } = await supabase.from('products').select().eq('historic', true);

// Load previously used products from Supabase and set recommendations
if (historic) {
// remove duplicate names
const combinedHistoric = [...historic.map((item: any) => item.name), ...groceryOptions];
const uniqueHistoric = [...new Set(combinedHistoric)];
setGroceryOptions(uniqueHistoric);
}

// Group products by category
if (products) {
const grouped: any = categories?.map((category: any) => {
const items = products.filter((product: any) => product.category === category.id);
return { ...category, data: items };
});
setListItems(grouped);
}
};
fetchData();
}, []);

// Add item to shopping list
const onAddItem = async (name: string, categoryId: number) => {
const result = await supabase
.from('products')
.insert([{ name, category: categoryId, user_id: user?.id }])
.select();

// Add item to state
if (result.data) {
const category = listItems.find((category) => category.id === categoryId);
if (category) {
category.data.push(result.data[0]);
setListItems((prev) => [...prev]);
}
}
};

// Shopping List Item Row
const renderGroceryRow: ListRenderItem<any> = ({ item }) => {
const onSelect = async (grocery: any) => {
// Remove item by setting historic to true
await supabase.from('products').update({ historic: true }).eq('id', grocery.id);

// Remove item from state
const category = listItems.find((category) => category.id === grocery.category);
if (category) {
category.data = category.data.filter((item: any) => item.id !== grocery.id);
setListItems((prev) => [...prev]);
}
};

return (
<TouchableOpacity
onPress={() => onSelect(item)}
style={[styles.groceryRow, { backgroundColor: '#0c3824' }]}>
<Text style={styles.groceryName}>{item.name}</Text>
<Ionicons name="checkmark" size={24} color="white" />
</TouchableOpacity>
);
};

return (
<View style={styles.container}>
{listItems.length > 0 && (
<SectionList
contentContainerStyle={{ paddingBottom: 150 }}
sections={listItems}
stickySectionHeadersEnabled={false}
renderItem={renderGroceryRow}
renderSectionHeader={({ section: { category } }) => (
<Text style={styles.sectionHeader}>{category}</Text>
)}
/>
)}

<BottomGrocerySheet
listItems={listItems}
groceryOptions={groceryOptions}
onItemSelected={(item, categoryId) => onAddItem(item, categoryId)}
/>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#2a2a2a',
},
groceryRow: {
flexDirection: 'row',
backgroundColor: '#2b825b',
padding: 10,
marginHorizontal: 16,
marginVertical: 4,
borderRadius: 4,
},
groceryName: {
color: '#fff',
fontSize: 20,
flex: 1,
},
sectionHeader: {
color: '#fff',
fontSize: 16,
fontWeight: 'bold',
marginHorizontal: 16,
marginTop: 20,
},
});
55 changes: 0 additions & 55 deletions app/(tabs)/_layout.tsx

This file was deleted.

31 changes: 0 additions & 31 deletions app/(tabs)/index.tsx

This file was deleted.

31 changes: 0 additions & 31 deletions app/(tabs)/two.tsx

This file was deleted.

46 changes: 0 additions & 46 deletions app/+html.tsx

This file was deleted.

Loading

0 comments on commit 48633b4

Please sign in to comment.