diff --git a/App.tsx b/App.tsx index 8bd19b2..5e83874 100644 --- a/App.tsx +++ b/App.tsx @@ -1,8 +1,13 @@ import * as React from 'react'; +import { useEffect } from 'react'; import { NavigationContainer } from '@react-navigation/native'; import NavigationBar from '@/navigation/BottomTabNavigator'; +import getDataOnce from '@/supabase/getDataOnce'; export default function App() { + useEffect(() => { + getDataOnce(); + }, []); return ( diff --git a/package-lock.json b/package-lock.json index eefaaa2..0834e67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "mobile-app-template", "version": "1.0.0", "dependencies": { + "@react-native-async-storage/async-storage": "^2.1.2", "@react-native/metro-config": "^0.76.3", "@react-navigation/bottom-tabs": "^7.0.6", "@react-navigation/drawer": "^7.0.6", @@ -3783,6 +3784,18 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.1.2.tgz", + "integrity": "sha512-dvlNq4AlGWC+ehtH12p65+17V0Dx7IecOWl6WanF2ja38O1Dcjjvn7jVzkUHJ5oWkQBlyASurTPlTHgKXyYiow==", + "license": "MIT", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.65 <1.0" + } + }, "node_modules/@react-native/assets-registry": { "version": "0.76.1", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.76.1.tgz", @@ -10109,6 +10122,15 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -11058,6 +11080,18 @@ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", "license": "MIT" }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "license": "MIT", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", diff --git a/package.json b/package.json index 580d658..2714bbd 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "pre-commit": "(npm run tsc || true) && (npm run lint:check || true) && npm run prettier:check" }, "dependencies": { + "@react-native-async-storage/async-storage": "^2.1.2", "@react-native/metro-config": "^0.76.3", "@react-navigation/bottom-tabs": "^7.0.6", "@react-navigation/drawer": "^7.0.6", @@ -27,8 +28,8 @@ "expo": "^52.0.5", "expo-av": "~15.0.1", "expo-status-bar": "~2.0.0", - "metro-config": "^0.81.0", "expo-video": "~2.0.2", + "metro-config": "^0.81.0", "react": "^18.3.1", "react-native": "0.76.1", "react-native-dotenv": "^3.4.11", diff --git a/src/assets/images/bottom-carrot.svg b/src/assets/images/bottom-carrot.svg new file mode 100644 index 0000000..46ede57 --- /dev/null +++ b/src/assets/images/bottom-carrot.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/images/left-carrot.svg b/src/assets/images/left-carrot.svg new file mode 100644 index 0000000..6bba91e --- /dev/null +++ b/src/assets/images/left-carrot.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/right-carrot.svg b/src/assets/images/right-carrot.svg new file mode 100644 index 0000000..e5e340d --- /dev/null +++ b/src/assets/images/right-carrot.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/videos/da_link.mp4 b/src/assets/videos/da_link.mp4 deleted file mode 100644 index a44a47c..0000000 Binary files a/src/assets/videos/da_link.mp4 and /dev/null differ diff --git a/src/components/HFHPage/index.tsx b/src/components/HFHPage/index.tsx new file mode 100644 index 0000000..7ed45f5 --- /dev/null +++ b/src/components/HFHPage/index.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { View } from 'react-native'; +import { DrawerScreenProps } from '@react-navigation/drawer'; +import HealingPage from 'src/screens/HealingResources/HFHGuide'; + +type RootDrawerParamList = { + DynamicHealingPage: { id: string }; +}; + +function DynamicHealingPage({ + route, + navigation, +}: DrawerScreenProps) { + const { id } = route.params; + + return ( + + + + ); +} + +export default DynamicHealingPage; diff --git a/src/navigation/BottomTabNavigator.tsx b/src/navigation/BottomTabNavigator.tsx index 398c1e4..579f306 100644 --- a/src/navigation/BottomTabNavigator.tsx +++ b/src/navigation/BottomTabNavigator.tsx @@ -15,11 +15,10 @@ import { colors } from 'src/styles/colors'; import HealingResourcesNavigator from './stacks/HealingResourcesNavigator'; import LegalRightsNavigator from './stacks/LegalRightsNavigator'; import SeekHelpNavigator from './stacks/SeekHelpNavigator'; -import { BottomTabParams } from './types'; const initialRouteName = 'Healing'; -const Tab = createBottomTabNavigator(); +const Tab = createBottomTabNavigator(); export default function NavigationBar() { return ( @@ -35,7 +34,7 @@ export default function NavigationBar() { component={HomeScreen} options={{ headerTitle: '', - tabBarIcon: ({ focused }) => + tabBarIcon: ({ focused }: { focused: boolean }) => focused ? : , headerStyle: { backgroundColor: '#F7F9FC', @@ -59,7 +58,7 @@ export default function NavigationBar() { name="Healing" component={HealingResourcesNavigator} options={{ - tabBarIcon: ({ focused }) => + tabBarIcon: ({ focused }: { focused: boolean }) => focused ? ( ) : ( @@ -72,7 +71,7 @@ export default function NavigationBar() { component={LegalRightsNavigator} options={{ tabBarLabel: 'Legal Rights', - tabBarIcon: ({ focused }) => + tabBarIcon: ({ focused }: { focused: boolean }) => focused ? : , }} /> @@ -81,7 +80,7 @@ export default function NavigationBar() { component={SeekHelpNavigator} options={{ tabBarLabel: 'Seek Help', - tabBarIcon: ({ focused }) => + tabBarIcon: ({ focused }: { focused: boolean }) => focused ? : , }} /> diff --git a/src/navigation/HopeHealingNavigator.tsx b/src/navigation/HopeHealingNavigator.tsx new file mode 100644 index 0000000..77fcdf5 --- /dev/null +++ b/src/navigation/HopeHealingNavigator.tsx @@ -0,0 +1,109 @@ +import React, { useEffect, useState } from 'react'; +import { Text, TouchableOpacity, View } from 'react-native'; +import { + DrawerContentComponentProps, + DrawerContentScrollView, +} from '@react-navigation/drawer'; +import BottomCarrot from 'src/assets/images/bottom-carrot.svg'; +import RightCarrot from 'src/assets/images/right-carrot.svg'; +import getDataOnce from '@/supabase/getDataOnce'; +import { DrawerItem } from '../types/types'; +import styles from './styles'; + +export default function HopeHealingNavigator( + props: DrawerContentComponentProps, +) { + const [drawerItems, setDrawerItems] = useState(null); + + useEffect(() => { + (async () => { + const data = await getDataOnce(); + setDrawerItems(data); + })(); + }, []); + + const [expandedSections, setExpandedSections] = useState< + Record + >({}); + const [selectedItemId, setSelectedItemId] = useState(null); + + const toggleSection = (title: string) => { + setExpandedSections(prev => ({ ...prev, [title]: !prev[title] })); + }; + + const handleNavigate = (id: string) => { + setSelectedItemId(id); + props.navigation.navigate('DynamicHealingPage', { id }); + }; + + if (!drawerItems) { + return null; + } + + return ( + + {drawerItems.map(item => { + const isExpanded = expandedSections[item.title]; + + const isParentSelected = item.mainPageId === selectedItemId; + + return ( + + { + handleNavigate(item.mainPageId); + toggleSection(item.title); + }} + > + {isExpanded ? : } + + {item.title} + + + + {isExpanded && + item.subItems?.map(sub => { + const isSubSelected = sub.pageId === selectedItemId; + + return ( + handleNavigate(sub.pageId)} + > + + {sub.title} + + + ); + })} + + ); + })} + + ); +} diff --git a/src/navigation/MainNavigator.tsx b/src/navigation/MainNavigator.tsx index 20ee37d..34d1829 100644 --- a/src/navigation/MainNavigator.tsx +++ b/src/navigation/MainNavigator.tsx @@ -1,10 +1,7 @@ import React from 'react'; import { NavigationContainer } from '@react-navigation/native'; -import { createNativeStackNavigator } from '@react-navigation/native-stack'; import BottomTabNavigator from './BottomTabNavigator'; -const Stack = createNativeStackNavigator(); - export default function AppNavigator() { return ( diff --git a/src/navigation/stacks/HealingResourcesNavigator.tsx b/src/navigation/stacks/HealingResourcesNavigator.tsx index eec5362..a387d4e 100644 --- a/src/navigation/stacks/HealingResourcesNavigator.tsx +++ b/src/navigation/stacks/HealingResourcesNavigator.tsx @@ -7,9 +7,8 @@ import BackButton from '@/components/BackButton'; import HealingResources from '@/screens/HealingResources'; import HealingCatalogue from '@/screens/HealingResources/HealingCatalogue'; import HopeHealingGuide from '@/screens/HealingResources/HopeHealingGuide/'; -import { HealingStackParams } from '../types'; -const HealingStack = createNativeStackNavigator(); +const HealingStack = createNativeStackNavigator(); export default function HealingResourcesNavigator() { return ( diff --git a/src/navigation/stacks/LegalRightsNavigator.tsx b/src/navigation/stacks/LegalRightsNavigator.tsx index e2e9d61..b7986b7 100644 --- a/src/navigation/stacks/LegalRightsNavigator.tsx +++ b/src/navigation/stacks/LegalRightsNavigator.tsx @@ -6,9 +6,8 @@ import { colors } from 'src/styles/colors'; import LegalRights from '@/screens/LegalRights'; import VideoPage from '@/screens/LegalRights/VideoPage'; import BackButton from '../../components/BackButton'; -import { LegalStackParams } from '../types'; -const LegalStack = createNativeStackNavigator(); +const LegalStack = createNativeStackNavigator(); export default function LegalRightsNavigator() { return ( diff --git a/src/navigation/stacks/SeekHelpNavigator.tsx b/src/navigation/stacks/SeekHelpNavigator.tsx index a737c34..681deb2 100644 --- a/src/navigation/stacks/SeekHelpNavigator.tsx +++ b/src/navigation/stacks/SeekHelpNavigator.tsx @@ -4,9 +4,8 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack'; import Logo from 'src/assets/images/logo.svg'; import { colors } from 'src/styles/colors'; import SeekHelp from '@/screens/SeekHelp'; -import { SeekHelpStackParams } from '../types'; -const SeekHelpStack = createNativeStackNavigator(); +const SeekHelpStack = createNativeStackNavigator(); export default function SeekHelpNavigator() { return ( diff --git a/src/navigation/styles.ts b/src/navigation/styles.ts new file mode 100644 index 0000000..badd756 --- /dev/null +++ b/src/navigation/styles.ts @@ -0,0 +1,34 @@ +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + drawerLabelText: { + fontSize: 20, + color: '#444', + }, + selectedDrawerLabelText: { + fontWeight: 'bold', + color: '#444', + fontSize: 20, + }, + selectedLabelText: { + fontWeight: 'bold', + color: '#444', + fontSize: 18, + }, + labelText: { + fontSize: 18, + color: '#444', + }, + navLabelContainer: { + flexDirection: 'row', + alignItems: 'center', + columnGap: 10, + borderRadius: 10, + padding: 15, + }, + subitemContainer: { + paddingLeft: 32, + padding: 10, + borderRadius: 10, + }, +}); diff --git a/src/screens/HealingResources/HFHGuide/index.tsx b/src/screens/HealingResources/HFHGuide/index.tsx index 744181e..7e432d6 100644 --- a/src/screens/HealingResources/HFHGuide/index.tsx +++ b/src/screens/HealingResources/HFHGuide/index.tsx @@ -1,23 +1,25 @@ import React, { useEffect, useState } from 'react'; -import { ScrollView, Text, View } from 'react-native'; +import { ScrollView, Text, TouchableOpacity, View } from 'react-native'; import RenderHTML, { MixedStyleRecord } from 'react-native-render-html'; import { useFonts } from 'expo-font'; -import { HealingScreenProps } from '@/navigation/types'; +import LeftCarrot from 'src/assets/images/left-carrot.svg'; +import RightCarrot from 'src/assets/images/right-carrot.svg'; import { - getNextSubheadingId, + getNeighboringSubheadingIds, getSubheadingById, } from '@/supabase/queries/generalQueries'; import styles from './styles'; export default function HFHGuide({ + id, navigation, - route, -}: HealingScreenProps<'HopeForHealingGuide'>) { +}: { + id: string; + navigation: any; +}) { const [htmlContent, setHtmlContent] = useState(null); - const [nextId, setNextId] = useState( - '7012e24a-894e-4972-9dcc-612666bff21e', - ); - const { id } = route.params; + const [nextId, setNextId] = useState('placeholder'); + const [prevId, setPrevId] = useState('placeholder'); const [fontsLoaded] = useFonts({ 'Roboto Serif': require('src/assets/fonts/Roboto_Serif/RobotoSerif-Regular.ttf'), @@ -27,8 +29,9 @@ export default function HFHGuide({ useEffect(() => { const fetchHtml = async () => { - const nextId = await getNextSubheadingId(id); - setNextId(nextId); + const [theNextId, thePrevId] = await getNeighboringSubheadingIds(id); + setNextId(theNextId); + setPrevId(thePrevId); const url = await getSubheadingById(id); if (url) { const response = await fetch(url); @@ -37,12 +40,20 @@ export default function HFHGuide({ } }; fetchHtml(); - }, []); + }, [id]); + + const handleNav = (prev: boolean) => { + if (prev) { + navigation.navigate('DynamicHealingPage', { id: prevId }); + } else { + navigation.navigate('DynamicHealingPage', { id: nextId }); + } + }; return ( - {fontsLoaded ? ( // Check if fonts are loaded + {fontsLoaded ? ( htmlContent ? ( Loading content... )} + + {prevId ? ( + handleNav(true)} + > + + Back + + ) : ( + + )} + {nextId ? ( + handleNav(false)} + > + Next + + + ) : ( + + )} + ); @@ -70,7 +105,7 @@ const htmlStyles: MixedStyleRecord = { p: { fontFamily: 'Roboto Serif', fontSize: 20, - fontWeight: '200', // Use a valid string value as per the error + fontWeight: '200', color: '#444', lineHeight: 30, marginVertical: 6, diff --git a/src/screens/HealingResources/HFHGuide/styles.ts b/src/screens/HealingResources/HFHGuide/styles.ts index a8c9080..8663d21 100644 --- a/src/screens/HealingResources/HFHGuide/styles.ts +++ b/src/screens/HealingResources/HFHGuide/styles.ts @@ -3,6 +3,7 @@ import { StyleSheet } from 'react-native'; export default StyleSheet.create({ container: { flex: 1, + width: '100%', backgroundColor: '#FFFFFF', }, scrollView: { @@ -12,4 +13,24 @@ export default StyleSheet.create({ fontFamily: 'Roboto Serif', color: '#444', }, + buttonContainer: { + justifyContent: 'space-between', + flexDirection: 'row', + color: '#444', + }, + button: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + borderRadius: 15, + backgroundColor: '#EDF0F5', + width: 125, + height: 43, + columnGap: 20, + }, + buttonText: { + color: '#4C4C4C', + fontSize: 16, + fontWeight: 500, + }, }); diff --git a/src/screens/HealingResources/HopeHealingGuide/index.tsx b/src/screens/HealingResources/HopeHealingGuide/index.tsx index 8ba2d56..035cb7d 100644 --- a/src/screens/HealingResources/HopeHealingGuide/index.tsx +++ b/src/screens/HealingResources/HopeHealingGuide/index.tsx @@ -1,170 +1,145 @@ -import React, { useState } from 'react'; -import { Text, TouchableOpacity, View } from 'react-native'; -import { createDrawerNavigator } from '@react-navigation/drawer'; -import styles from './styles'; - -type RootDrawerParamList = { - Welcome: undefined; - ChapterTwo: undefined; - ChapterThree: undefined; - WelcomeSectionOne: undefined; - WelcomeSectionTwo: undefined; - SectionOne: undefined; - SectionTwo: undefined; - Resource: undefined; -}; +// export default function HopeHealingGuide() { +// const [isWelcomeOpen, setIsWelcomeOpen] = useState(true); +// const [isChapterOneOpen, setIsChapterOneOpen] = useState(false); -function Welcome() { - return ( - - Main Welcome Screen - - ); -} +// return ( +// +// ( +// setIsWelcomeOpen(!isWelcomeOpen)} +// > +// {isWelcomeOpen ? : } +// +// Welcome +// +// +// ), +// }} +// component={Welcome} +// /> +// {isWelcomeOpen && ( +// <> +// - Chapter 2 - - ); -} +// drawerLabel: () => ( +// +// A Few Words on Language +// +// ), +// }} +// component={WelcomeSectionOne} +// /> +// ( +// +// How to Use This Booklet +// +// ), +// }} +// /> +// +// )} +// ( +// setIsChapterOneOpen(!isChapterOneOpen)} +// > +// {isChapterOneOpen ? : } -function ChapterThree() { - return ( - - Chapter 3 - - ); -} +// +// Chapter 1 +// +// +// ), +// }} +// /> +// {isChapterOneOpen && ( +// <> +// - A Few Words on Language - - ); -} +// drawerLabel: () => ( +// +// Making Sense of What Happened +// +// ), +// }} +// component={ChapterOneSectionOne} +// /> +// +// )} +// +// ); +// } -function WelcomeSectionTwo() { - return ( - - How to Use This Booklet - - ); -} - -function SectionOne() { - return ( - - Section One of Chapter One - - ); -} - -function SectionTwo() { - return ( - - Section Two of Chapter One - - ); -} +// HopeHealingGuide.tsx +import React from 'react'; +import { createDrawerNavigator } from '@react-navigation/drawer'; +import DynamicHealingPage from '@/components/HFHPage'; +import HopeHealingNavigator from '@/navigation/HopeHealingNavigator'; -const Drawer = createDrawerNavigator(); +const Drawer = createDrawerNavigator(); export default function HopeHealingGuide() { - const [isWelcomeOpen, setIsWelcomeOpen] = useState(true); - const [isChapterOneOpen, setIsChapterOneOpen] = useState(false); - return ( } screenOptions={{ - drawerType: 'front', + headerShown: false, + drawerType: 'slide', overlayColor: 'transparent', - headerTitle: '', + drawerStyle: { + width: '25%', + backgroundColor: '#F7F9FC', + }, }} > ( - setIsWelcomeOpen(!isWelcomeOpen)}> - - Welcome - - - ), - }} - component={Welcome} - /> - {isWelcomeOpen && ( - <> - ( - - A Few Words on Language - - ), - }} - component={WelcomeSectionOne} - /> - ( - - How to Use This Booklet - - ), - }} - /> - - )} - ( - setIsChapterOneOpen(!isChapterOneOpen)} - > - - Chapter 2 - - - ), - }} - /> - {isChapterOneOpen && ( - <> - ( - Section One - ), - }} - /> - ( - Section Two - ), - }} - /> - - )} - ); diff --git a/src/screens/HealingResources/HopeHealingGuide/styles.ts b/src/screens/HealingResources/HopeHealingGuide/styles.ts index ae0e3e0..35a6416 100644 --- a/src/screens/HealingResources/HopeHealingGuide/styles.ts +++ b/src/screens/HealingResources/HopeHealingGuide/styles.ts @@ -2,15 +2,29 @@ import { StyleSheet } from 'react-native'; export default StyleSheet.create({ drawerItem: { - flex: 1, alignItems: 'center', justifyContent: 'center', }, drawerLabelText: { + fontSize: 20, + color: '#444', + }, + selectedDrawerLabelText: { fontWeight: 'bold', + color: '#444', + fontSize: 20, }, subsectionLabelText: { - color: '#666', - paddingLeft: 30, + color: '#444444BF', + opacity: 0.75, + paddingLeft: 32, + fontSize: 18, + }, + navLabelContainer: { + flexDirection: 'row', + alignItems: 'center', + columnGap: 10, + // borderRadius: 10, + // backgroundColor: '#E6EAEF', }, }); diff --git a/src/screens/LegalRights/VideoPage/index.tsx b/src/screens/LegalRights/VideoPage/index.tsx index 052d68f..d7eef8d 100644 --- a/src/screens/LegalRights/VideoPage/index.tsx +++ b/src/screens/LegalRights/VideoPage/index.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useRef, useState } from 'react'; import { Image, Pressable, Text, View } from 'react-native'; import { useVideoPlayer, VideoView } from 'expo-video'; -import { useFocusEffect } from '@react-navigation/native'; import leftArrow from '@/assets/images/left-arrow.png'; import rightArrow from '@/assets/images/right-arrow.png'; import { LegalScreenProps } from '@/navigation/types'; diff --git a/src/screens/SeekHelp/ResourceList/index.tsx b/src/screens/SeekHelp/ResourceList/index.tsx new file mode 100644 index 0000000..12dba43 --- /dev/null +++ b/src/screens/SeekHelp/ResourceList/index.tsx @@ -0,0 +1,108 @@ +import React, { useEffect, useState } from 'react'; +import { ScrollView, Text, TouchableOpacity, View } from 'react-native'; +import ResourceT from '@/components/Resource/Resource'; +import { getSeekHelpData } from '@/supabase/queries/generalQueries'; +import { Resource as ResourceType } from '@/types/types'; +import { styles } from './styles'; + +export default function ResourceList() { + const filters = [ + 'General Resources', + 'Health Organizations', + 'LGBT Organizations', + 'Legal Services', + 'Government Resources', + ]; + + const tagMapping: Record = { + 'General Resources': ['General'], + 'Health Organizations': ['Health'], + 'LGBT Organizations': ['LGBT'], + 'Legal Services': ['Legal'], + 'Government Resources': ['Government'], + }; + + const [resources, setResources] = useState([]); + const [filteredResources, setFilteredResources] = useState( + [], + ); + const [selectedFilter, setSelectedFilter] = useState(null); + + useEffect(() => { + fetchData(); + }, []); + + const fetchData = async () => { + try { + const data = await getSeekHelpData(); + setResources(data); + setFilteredResources(data); + } catch (error) { + console.error('Error fetching data:', error); + } + }; + + const applyFilter = (filter: string) => { + setSelectedFilter(filter); + const tags = tagMapping[filter as keyof typeof tagMapping]; + // iterates through resources to check if the tags are equal to the filter + const filtered = resources.filter(resource => + resource.tags.split(',').some((tag: string) => tags.includes(tag.trim())), + ); + setFilteredResources(filtered); + }; + + return ( + + + + Select Resources Type: + + {filters.map((filter, index) => ( + applyFilter(filter)} + > + + {filter} + + + ))} + + + + {filteredResources.length > 0 ? ( + filteredResources + .sort(function (a, b) { + return a.org_name.localeCompare(b.org_name); + }) + .map((resource, index) => ( + + )) + ) : ( + No resources found + )} + + + ); +} diff --git a/src/screens/SeekHelp/ResourceList/styles.ts b/src/screens/SeekHelp/ResourceList/styles.ts new file mode 100644 index 0000000..3d94bc7 --- /dev/null +++ b/src/screens/SeekHelp/ResourceList/styles.ts @@ -0,0 +1,72 @@ +import { StyleSheet } from 'react-native'; + +export const styles = StyleSheet.create({ + container: { + display: 'flex', + flexDirection: 'row', + height: '100%', + }, + leftPanel: { + flexDirection: 'column', + width: '20%', + backgroundColor: '#f7f9fc', + paddingHorizontal: 10, + }, + rightPanel: { + display: 'flex', + flexDirection: 'column', + width: '80%', + height: '100%', + backgroundColor: '#ffffff', + padding: 10, + }, + filterButton: { + backgroundColor: '#f7f9fc', + borderRadius: 5, + paddingVertical: 20, + paddingHorizontal: 25, + width: '100%', + alignItems: 'flex-start', + justifyContent: 'center', + borderWidth: 1, + borderColor: '#f7f9fc', + }, + selectedFilterButton: { + backgroundColor: '#e8e8e8', + borderColor: '#e8e8e8', + borderRadius: 10, + marginVertical: 10, + marginLeft: 5, + paddingVertical: 10, + paddingHorizontal: 20, + width: '95%', + alignItems: 'flex-start', + borderWidth: 1, + justifyContent: 'center', + shadowColor: '#000', + shadowOpacity: 0.1, + shadowRadius: 5, + shadowOffset: { width: 0, height: 2 }, + }, + selectedButtonText: { + fontSize: 16, + color: '#444', + fontWeight: '600', + }, + buttonText: { + fontSize: 16, + color: '#444', + }, + selectTextContainer: { + marginVertical: 10, + marginHorizontal: 15, + }, + selectText: { + color: '#9B9B9B', + fontFamily: 'Inter', + fontSize: 14, + fontWeight: '600', + lineHeight: 21, + letterSpacing: -0.154, + }, +}); diff --git a/src/supabase/getDataOnce.ts b/src/supabase/getDataOnce.ts new file mode 100644 index 0000000..89948dd --- /dev/null +++ b/src/supabase/getDataOnce.ts @@ -0,0 +1,28 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { transformToDrawerItems } from '../utils/utils'; +import supabase from './createClient'; + +const DATA_STORAGE_KEY = 'mySupabaseData'; // is this good practice??? + +const getDataOnce = async () => { + try { + const storedDataString = await AsyncStorage.getItem(DATA_STORAGE_KEY); + + if (storedDataString) { + return JSON.parse(storedDataString); + } + + const { data, error } = await supabase.from('hfh_subheading').select('*'); + + if (error) throw error; + + const formattedData = transformToDrawerItems(data); + + await AsyncStorage.setItem(DATA_STORAGE_KEY, JSON.stringify(formattedData)); + } catch (err) { + console.error('Failed to fetch or store data:', err); + return null; + } +}; + +export default getDataOnce; diff --git a/src/supabase/queries/generalQueries.tsx b/src/supabase/queries/generalQueries.tsx index 22e849e..e88fb49 100644 --- a/src/supabase/queries/generalQueries.tsx +++ b/src/supabase/queries/generalQueries.tsx @@ -54,9 +54,9 @@ export const getSubheadingById = async (id: subheadingId): Promise => { return gethfhHTML(htmlLink); }; -export const getNextSubheadingId = async ( +export const getNeighboringSubheadingIds = async ( id: subheadingId, -): Promise => { +): Promise => { const { data, error } = await supabase .from('hfh_subheading') .select() @@ -65,5 +65,6 @@ export const getNextSubheadingId = async ( throw error; } const nextId = data[0].next; - return nextId; + const prevId = data[0].prev; + return [nextId, prevId]; }; diff --git a/src/types/types.tsx b/src/types/types.tsx index 9e3ebca..d470501 100644 --- a/src/types/types.tsx +++ b/src/types/types.tsx @@ -32,6 +32,27 @@ export type VideoResource = { video_id: string; }; +// -------- From hfh_subheading table ---------- +export type HFHPage = { + id: string; + html_link: string; + next: string; + prev: string; + chapter_name: string; + chapter_number: number; + subheading_number: number; +}; + +export type DrawerItem = { + title: string; + mainPageId: string; + subItems: { + title: string; + pageId: string; + }[]; +}; +// ------------------------------------------------ + export interface VideoSectionItemProps { section: VideoResource; onPress: (pageNumber: number, language: string) => void; diff --git a/src/utils/utils.ts b/src/utils/utils.ts new file mode 100644 index 0000000..baae06f --- /dev/null +++ b/src/utils/utils.ts @@ -0,0 +1,42 @@ +import { DrawerItem, HFHPage } from '@/types/types'; + +export const transformToDrawerItems = (data: HFHPage[]): DrawerItem[] => { + const chapters = new Map(); + + for (const page of data) { + const { chapter_number, subheading_number, chapter_name, id } = page; + + // main chapter heading + if (subheading_number === 0) { + chapters.set(chapter_number, { + title: chapter_name, + mainPageId: id, + subItems: [], + }); + } else { + const chapter = chapters.get(chapter_number); + if (chapter) { + chapter.subItems.push({ + title: chapter_name, + pageId: id, + }); + } else { + // chapter doesn't exist yet - create a placeholder for now + chapters.set(chapter_number, { + title: `Chapter ${chapter_number}`, // will be overwritten + mainPageId: '', // will be overwritten + subItems: [ + { + title: chapter_name, + pageId: id, + }, + ], + }); + } + } + } + const formattedData = Array.from(chapters.entries()) + .sort((a, b) => a[0] - b[0]) + .map(([, item]) => item); + return formattedData; +};