From f461657f9ba5f7e58134d1ad73145a6ef095c79e Mon Sep 17 00:00:00 2001 From: Katy Nguyen <123796321+katyhonguyen@users.noreply.github.com> Date: Wed, 11 Dec 2024 09:31:45 -0800 Subject: [PATCH] feat: lofi search screen (#15) * Updated tree search screen. --------- Co-authored-by: Chris Torres --- app.json | 1 + package-lock.json | 192 ++------------------------ package.json | 2 + src/components/SearchBar.tsx | 23 +++ src/components/TreeFilter.tsx | 0 src/screens/Contact/Contact.tsx | 104 +++++++------- src/screens/Contact/Directory.tsx | 0 src/screens/Contact/styles.ts | 41 ++---- src/screens/TreeSearch/TreeSearch.tsx | 141 ++++++++++++------- src/screens/TreeSearch/styles.ts | 83 ++++++++--- src/screens/login/styles.ts | 53 +++++++ 11 files changed, 313 insertions(+), 327 deletions(-) create mode 100644 src/components/SearchBar.tsx create mode 100644 src/components/TreeFilter.tsx create mode 100644 src/screens/Contact/Directory.tsx create mode 100644 src/screens/login/styles.ts diff --git a/app.json b/app.json index 6df8ecf..d4f671f 100644 --- a/app.json +++ b/app.json @@ -8,6 +8,7 @@ "orientation": "portrait", "icon": "./assets/bp-icon.png", "userInterfaceStyle": "light", + "newArchEnabled": true, "plugins": [ [ "expo-camera", diff --git a/package-lock.json b/package-lock.json index 651168a..e719801 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "expo-constants": "~17.0.3", "expo-crypto": "~14.0.1", "expo-dev-client": "~5.0.6", + "expo-dev-launcher": "^5.0.14", "expo-device": "~7.0.1", "expo-font": "^13.0.1", "expo-linking": "~7.0.3", @@ -69,6 +70,7 @@ "@eslint/object-schema": "^2.1.4", "@ianvs/prettier-plugin-sort-imports": "^4.4.0", "@types/react": "~18.3.12", + "@types/react-dom": "^18.3.1", "@typescript-eslint/eslint-plugin": "^8.18.0", "@typescript-eslint/parser": "^8.18.0", "babel-plugin-module-resolver": "^5.0.2", @@ -5331,6 +5333,16 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-dom": { + "version": "18.3.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz", + "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, "node_modules/@types/react-native": { "version": "0.72.8", "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.72.8.tgz", @@ -11808,186 +11820,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.27.0.tgz", - "integrity": "sha512-0+mZa54IlcNAoQS9E0+niovhyjjQWEMrwW0p2sSdLRhLDc8LMQ/b67z7+B5q4VmjYCMSfnFi3djAAQFIDuj/Tg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.27.0.tgz", - "integrity": "sha512-n1sEf85fePoU2aDN2PzYjoI8gbBqnmLGEhKq7q0DKLj0UTVmOTwDC7PtLcy/zFxzASTSBlVQYJUhwIStQMIpRA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.27.0.tgz", - "integrity": "sha512-MUMRmtdRkOkd5z3h986HOuNBD1c2lq2BSQA1Jg88d9I7bmPGx08bwGcnB75dvr17CwxjxD6XPi3Qh8ArmKFqCA==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.27.0.tgz", - "integrity": "sha512-cPsxo1QEWq2sfKkSq2Bq5feQDHdUEwgtA9KaB27J5AX22+l4l0ptgjMZZtYtUnteBofjee+0oW1wQ1guv04a7A==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.27.0.tgz", - "integrity": "sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.27.0.tgz", - "integrity": "sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.27.0.tgz", - "integrity": "sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.27.0.tgz", - "integrity": "sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.27.0.tgz", - "integrity": "sha512-/OJLj94Zm/waZShL8nB5jsNj3CfNATLCTyFxZyouilfTmSoLDX7VlVAmhPHoZWVFp4vdmoiEbPEYC8HID3m6yw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", diff --git a/package.json b/package.json index 754ae0b..f360afb 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "expo-constants": "~17.0.3", "expo-crypto": "~14.0.1", "expo-dev-client": "~5.0.6", + "expo-dev-launcher": "^5.0.14", "expo-device": "~7.0.1", "expo-font": "^13.0.1", "expo-linking": "~7.0.3", @@ -75,6 +76,7 @@ "@eslint/object-schema": "^2.1.4", "@ianvs/prettier-plugin-sort-imports": "^4.4.0", "@types/react": "~18.3.12", + "@types/react-dom": "^18.3.1", "@typescript-eslint/eslint-plugin": "^8.18.0", "@typescript-eslint/parser": "^8.18.0", "babel-plugin-module-resolver": "^5.0.2", diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx new file mode 100644 index 0000000..b48d286 --- /dev/null +++ b/src/components/SearchBar.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { TextInput, View } from 'react-native'; +import { styles } from '../screens/TreeSearch/styles'; + +type SearchBarProps = { + value: string; + onChange: (text: string) => void; +}; + +const SearchBar: React.FC = ({ value, onChange }) => { + return ( + + + + ); +}; + +export default SearchBar; diff --git a/src/components/TreeFilter.tsx b/src/components/TreeFilter.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/screens/Contact/Contact.tsx b/src/screens/Contact/Contact.tsx index 8e9fea6..cebb8fd 100644 --- a/src/screens/Contact/Contact.tsx +++ b/src/screens/Contact/Contact.tsx @@ -1,8 +1,18 @@ -import React from 'react'; -import { Image, ScrollView, Text, View } from 'react-native'; +/* import React from 'react'; +import { + Linking, + ScrollView, + Text, + TouchableOpacity, + View, +} from 'react-native'; import { NativeStackScreenProps } from '@react-navigation/native-stack'; -import GoogleSignOutButton from '@/components/GoogleSignOutButton/GoogleSignOutButton'; import { ContactStackParamList } from '@/types/navigation'; +import ArrowRight from '../svg/arrow-right.svg'; +import Call from '../svg/call.svg'; +import Location from '../svg/location.svg'; +import OcfLogo from '../svg/ocf-logo.svg'; +import Website from '../svg/website.svg'; import { styles } from './styles'; type ContactScreenProps = NativeStackScreenProps< @@ -10,58 +20,56 @@ type ContactScreenProps = NativeStackScreenProps< 'Contact' >; -export default function ContactScreen({ - navigation, - route, -}: ContactScreenProps) { - return ( - - - +export default function ContactScreen({ navigation }: ContactScreenProps) { + const openLink = (url: string) => { + Linking.openURL(url).catch(err => console.error("Failed to open URL:", err)); + }; - - - - - {/* temporary button */} - + const openLocation = () => { + const locationUrl = 'https://www.google.com/maps/place/Our+City+Forest/@37.590136,-122.3968825,10z/data=!4m20!...'; + Linking.openURL(locationUrl).catch(err => console.error("Failed to open location:", err)); + }; + return ( + + + Contact Us - - Location - - 123 Berkeley Way, San Jose, CA 95035 - - + + navigation.navigate('Directory')} + > + + + Directory + + + - - Hours - M-TH | 9 AM - 12 PM - + openLink('https://www.ourcityforest.org/')} + > + + + Website + + + - - Call - 123 - 456 - 7890 - - - - Email - - nurserymanager@ourcityforest.org - - - - - Instagram - Facebook - + + + + Visit Us + + + ); -} +}; + + */ diff --git a/src/screens/Contact/Directory.tsx b/src/screens/Contact/Directory.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/screens/Contact/styles.ts b/src/screens/Contact/styles.ts index 0034850..2d402f6 100644 --- a/src/screens/Contact/styles.ts +++ b/src/screens/Contact/styles.ts @@ -9,10 +9,20 @@ export const styles = StyleSheet.create({ backgroundColor: colors.white, }, - searchContainer: { - paddingTop: 32, - paddingLeft: 27, - paddingRight: 27, + linksButton: { + width: '100%', + height: 66, + color: 'gray', + }, + + contactIcons: { + width: 28, + height: 28, + }, + + ocfLogo: { + width: 93, + height: 110, }, contactHeader: { @@ -22,29 +32,6 @@ export const styles = StyleSheet.create({ textAlign: 'left', }, - imageContainer: { - width: '100%', - aspectRatio: 8 / 9, - position: 'relative', - top: 0, - left: 0, - }, - - contactImage: { - width: '100%', - height: '70%', - resizeMode: 'cover', - }, - - contactOverlay: { - position: 'absolute', - top: 0, - bottom: 0, - left: 0, - right: 0, - backgroundColor: 'rgba(0, 0, 0, 0.5)', - }, - contactInfo: { position: 'absolute', width: '100%', diff --git a/src/screens/TreeSearch/TreeSearch.tsx b/src/screens/TreeSearch/TreeSearch.tsx index 7f20036..1289e4e 100644 --- a/src/screens/TreeSearch/TreeSearch.tsx +++ b/src/screens/TreeSearch/TreeSearch.tsx @@ -1,14 +1,9 @@ import React, { useEffect, useState } from 'react'; -import { - FlatList, - ImageBackground, - ScrollView, - Text, - View, -} from 'react-native'; +import { FlatList, ImageBackground, Text, View } from 'react-native'; import { NativeStackScreenProps } from '@react-navigation/native-stack'; import { HomeStackParamList } from '@/types/navigation'; -import { fetchTreeData } from '../../supabase/client'; +import SearchBar from '../../components/searchBar'; +import { supabase } from '../../supabase/client'; import { styles } from './styles'; type TreeSearchScreenProps = NativeStackScreenProps< @@ -16,62 +11,108 @@ type TreeSearchScreenProps = NativeStackScreenProps< 'TreeSearch' >; -export default function TreeSearchScreen({ - navigation, -}: TreeSearchScreenProps) { - type Tree = { - tree_id: number; - species: string; - row: number; - bank: number; - image_url?: string; - }; +type TreeItem = { + tree_id: number; + species: string; + image_link: string; + sold: boolean; + stockCount: number; +}; - const [trees, setTrees] = useState([]); +export default function TreeSearch({ navigation }: TreeSearchScreenProps) { + const [trees, setTrees] = useState([]); + const [searchQuery, setSearchQuery] = useState(''); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); useEffect(() => { const loadTreeData = async () => { - const treeData = await fetchTreeData(); - console.log('Fetched trees:', treeData); - if (treeData) { - setTrees(treeData); + try { + setLoading(true); + const { data, error } = await supabase + .from('trees') + .select('tree_id, species, sold, species (name, image_link)'); + + if (error) { + throw new Error(`Error fetching tree data: ${error.message}`); + } + + const treeCounts: Record = data.reduce( + (acc, tree) => { + if (!tree.sold) { + acc[tree.species.name] = (acc[tree.species.name] || 0) + 1; + } + return acc; + }, + {} as Record, + ); + + const treesData: TreeItem[] = Object.keys(treeCounts).map( + speciesName => { + const treeSample = data.find( + tree => tree.species.name === speciesName, + ); + + return { + tree_id: treeSample?.tree_id || 0, + species: speciesName, + image_link: treeSample?.species?.image_link || '', + sold: false, + stockCount: treeCounts[speciesName], + }; + }, + ); + + setTrees(treesData); + setError(null); + } catch (err) { + console.error('Failed to fetch tree data:', err); + setError('Unable to load tree data. Please try again later.'); + } finally { + setLoading(false); } }; - loadTreeData(); }, []); - const renderTreeCard = ({ item }: { item: Tree }) => ( - - - - - {item.species} - - Row {item.row} - - Bank {item.bank} - - - + const filteredTrees = trees.filter(tree => + tree.species.toLowerCase().includes(searchQuery.toLowerCase()), + ); + + const renderTreeCard = ({ item }: { item: TreeItem }) => ( + + + + {item.species} + + {item.stockCount} in stock ); return ( - + <> - Trees Availibility - item.tree_id.toString()} - /> + Trees Inventory + - + + item.tree_id.toString()} + renderItem={renderTreeCard} + numColumns={2} + contentContainerStyle={styles.backgroundContainer} + ListEmptyComponent={ + + No trees found matching your search. + + } + /> + ); } diff --git a/src/screens/TreeSearch/styles.ts b/src/screens/TreeSearch/styles.ts index bd0b0bb..825e564 100644 --- a/src/screens/TreeSearch/styles.ts +++ b/src/screens/TreeSearch/styles.ts @@ -1,18 +1,38 @@ -import { StyleSheet } from 'react-native'; +import { Dimensions, StyleSheet } from 'react-native'; import colors from '@/styles/colors'; import typography from '@/styles/typography'; +const { width, height } = Dimensions.get('window'); + +const responsivePadding = width * 0.05; + export const styles = StyleSheet.create({ backgroundContainer: { flexGrow: 1, - flexDirection: 'column', backgroundColor: colors.white, + paddingTop: 18, + paddingLeft: 27, + paddingRight: 27, }, + // Search Component + searchContainer: { - paddingTop: 32, - paddingLeft: 27, - paddingRight: 27, + paddingLeft: responsivePadding, + paddingRight: responsivePadding, + paddingTop: height * 0.1, + paddingBottom: height * 0.02, + borderBottomWidth: 2, + borderBottomColor: colors.gray6, + }, + + searchBarInput: { + height: 42, + borderColor: colors.gray6, + borderWidth: 1, + borderRadius: 30, + paddingHorizontal: 10, + backgroundColor: colors.gray6, }, searchHeading: { @@ -22,44 +42,63 @@ export const styles = StyleSheet.create({ textAlign: 'left', }, - treeRow: { + // Tree Cards + + treeGrid: { + width: '100%', + paddingHorizontal: width * 0.04, flexDirection: 'row', flexWrap: 'wrap', - justifyContent: 'flex-start', + justifyContent: 'space-between', alignItems: 'flex-start', - padding: 10, }, treeCard: { - width: 160, - height: 182, - flexShrink: 0, - borderRadius: 5, - justifyContent: 'space-between', + width: width > 600 ? '30%' : '48%', + marginBottom: width * 0.02, + marginHorizontal: width * 0.01, + justifyContent: 'flex-start', + alignItems: 'flex-start', + borderRadius: 10, overflow: 'hidden', }, treeImage: { - width: 160, - height: 135, + width: '100%', + height: 150, flexShrink: 0, borderRadius: 5, resizeMode: 'cover', - backgroundColor: colors.gray3, + backgroundColor: colors.gray4, + marginBottom: 0, }, - treeDetails: { - alignItems: 'flex-start', - overflow: 'hidden', - flexDirection: 'row', + treeInfoContainer: { + justifyContent: 'space-between', + alignItems: 'center', + width: '100%', + marginTop: width * 0.01, }, treeName: { - flexShrink: 1, ...typography.mediumBold, + fontSize: width > 600 ? 20 : 18, + textAlign: 'left', + marginTop: width * 0.01, + flex: 1, + overflow: 'hidden', + }, + + treeStock: { + ...typography.smallRegular, + fontSize: width > 600 ? 16 : 14, + color: colors.gray3, + textAlign: 'left', }, - treeInfo: { + treeError: { ...typography.smallRegular, + fontSize: width > 600 ? 16 : 14, + padding: 0, }, }); diff --git a/src/screens/login/styles.ts b/src/screens/login/styles.ts new file mode 100644 index 0000000..1d9bec4 --- /dev/null +++ b/src/screens/login/styles.ts @@ -0,0 +1,53 @@ +import { StyleSheet } from 'react-native'; +import colors from '@/styles/colors'; +import typography from '@/styles/typography'; + +export const styles = StyleSheet.create({ + loginContainer: { + padding: 40, + flex: 1, + justifyContent: 'center', + }, + + loginText: { + ...typography.heading3, + color: colors.primary, + }, + + logoContainer: { + alignItems: 'center', + paddingTop: 20, + paddingBottom: 70, + }, + + logo: { + height: 200, + marginTop: 20, + marginBottom: 70, + }, + + button: { + backgroundColor: '#446127', + padding: 15, + borderRadius: 5, + alignItems: 'center', + marginBottom: 10, + }, + + buttonText: { + ...typography.largeBold, + color: colors.white, + }, + + adminLoginContainer: { + flex: 0, + flexDirection: 'row', + justifyContent: 'flex-end', + paddingTop: 10, + }, + + adminLoginText: { + ...typography.smallBold, + color: colors.gray3, + }, +});