From 571f0a8bfc8267658689d300dd9191e1bb52d536 Mon Sep 17 00:00:00 2001 From: Matt McCoy <59743922+MattCMcCoy@users.noreply.github.com> Date: Mon, 5 Feb 2024 16:54:11 -0500 Subject: [PATCH] feat: add a user context provider to the app (#24) * feat: userContext stuff idk man * refactor: general all around fixes (removing unused imports, fixing navigation errors) * refactor: all over the place with this commit, but added swagger docs for file upload * refactor: make context more general * refactor: utilize card and popup in the medlist FE --- backend/docs/docs.go | 43 ++++++++++- backend/docs/swagger.json | 43 ++++++++++- backend/docs/swagger.yaml | 27 +++++++ backend/schema/files/routes.go | 7 +- client/App.tsx | 62 +++------------- client/babel.config.js | 2 +- client/components/Card.tsx | 63 ++++------------ client/components/PopupModal.tsx | 72 +++++++------------ client/contexts/CareWalletContext.tsx | 53 ++++++++++++++ client/contexts/types.ts | 9 +++ client/navigation/AppNavigation.tsx | 28 ++++++++ .../navigation/AppStackBottomTabNavigator.tsx | 23 ++++++ client/navigation/Router.tsx | 11 +++ client/screens/Login.tsx | 16 ++--- client/screens/Medication.tsx | 43 +++++++++-- client/tailwind.config.js | 7 +- 16 files changed, 339 insertions(+), 170 deletions(-) create mode 100644 client/contexts/CareWalletContext.tsx create mode 100644 client/contexts/types.ts create mode 100644 client/navigation/AppNavigation.tsx create mode 100644 client/navigation/AppStackBottomTabNavigator.tsx create mode 100644 client/navigation/Router.tsx diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 8186cd9..72485a2 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -22,9 +22,24 @@ const docTemplate = `{ "file" ], "summary": "Upload a file", + "parameters": [ + { + "type": "file", + "description": "Body with file zip", + "name": "file_data", + "in": "formData", + "required": true + } + ], "responses": { "200": { - "description": "OK" + "description": "OK", + "schema": { + "$ref": "#/definitions/models.File" + } + }, + "400": { + "description": "Bad Request" } } } @@ -91,6 +106,32 @@ const docTemplate = `{ } }, "definitions": { + "models.File": { + "type": "object", + "properties": { + "file_id": { + "type": "integer" + }, + "file_name": { + "type": "string" + }, + "file_size": { + "type": "integer" + }, + "group_id": { + "type": "integer" + }, + "task_id": { + "type": "integer" + }, + "upload_by": { + "type": "integer" + }, + "upload_date": { + "type": "string" + } + } + }, "models.Medication": { "type": "object", "properties": { diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index abb0696..425d952 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -15,9 +15,24 @@ "file" ], "summary": "Upload a file", + "parameters": [ + { + "type": "file", + "description": "Body with file zip", + "name": "file_data", + "in": "formData", + "required": true + } + ], "responses": { "200": { - "description": "OK" + "description": "OK", + "schema": { + "$ref": "#/definitions/models.File" + } + }, + "400": { + "description": "Bad Request" } } } @@ -84,6 +99,32 @@ } }, "definitions": { + "models.File": { + "type": "object", + "properties": { + "file_id": { + "type": "integer" + }, + "file_name": { + "type": "string" + }, + "file_size": { + "type": "integer" + }, + "group_id": { + "type": "integer" + }, + "task_id": { + "type": "integer" + }, + "upload_by": { + "type": "integer" + }, + "upload_date": { + "type": "string" + } + } + }, "models.Medication": { "type": "object", "properties": { diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 2c37bdc..8b1d7e2 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -1,5 +1,22 @@ basePath: / definitions: + models.File: + properties: + file_id: + type: integer + file_name: + type: string + file_size: + type: integer + group_id: + type: integer + task_id: + type: integer + upload_by: + type: integer + upload_date: + type: string + type: object models.Medication: properties: medication_id: @@ -23,9 +40,19 @@ paths: /files/upload: post: description: Upload a file to database and S3 bucket + parameters: + - description: Body with file zip + in: formData + name: file_data + required: true + type: file responses: '200': description: OK + schema: + $ref: '#/definitions/models.File' + "400": + description: Bad Request summary: Upload a file tags: - file diff --git a/backend/schema/files/routes.go b/backend/schema/files/routes.go index bfae7aa..f32a874 100644 --- a/backend/schema/files/routes.go +++ b/backend/schema/files/routes.go @@ -28,10 +28,13 @@ func GetFileGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { // @summary Upload a file // @description Upload a file to database and S3 bucket // @tags file -// @success 200 +// @param file_data formData file true "Body with file zip" +// +// @success 200 {object} models.File +// @failure 400 // @router /files/upload [post] func (pg *PgModel) UploadFileRoute(c *gin.Context) { - // TODO: Ensure Swagger Knows about there bad request returns!!! + // TODO: Ensure Swagger Knows about the bad request returns!!! var file models.File if err := c.Bind(&file); err != nil { diff --git a/client/App.tsx b/client/App.tsx index 947b824..23205c7 100644 --- a/client/App.tsx +++ b/client/App.tsx @@ -1,57 +1,17 @@ import * as React from 'react'; -import { Text } from 'react-native'; -import { createNativeStackNavigator } from '@react-navigation/native-stack'; -import { NavigationContainer, NavigationProp } from '@react-navigation/native'; -import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; -import LoginPage from './screens/Login'; -import MedList from './screens/Medication'; -import Home from './assets/home.svg'; -import DocPickerButton from './components/DocPickerButton'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import Router from './navigation/Router'; +import CareWalletProvider from './contexts/CareWalletContext'; +import { PaperProvider } from 'react-native-paper'; -export type ScreenNames = ['BottomNav', 'Landing', 'TEMP-FileUpload', 'Login']; -export type RootStackParamList = Record; -export type StackNavigation = NavigationProp; - -const Stack = createNativeStackNavigator(); -const Tab = createBottomTabNavigator(); - -// TODO: figure out a way to do this better, I didnt enjoy this way of doing it in SaluTemp there HAS to be a better way export default function App() { return ( - - - - - - - - ); -} - -function Tabs() { - return ( - - , - tabBarLabel: () => Landing - }} - component={MedList} - /> - + + + + + + + ); } diff --git a/client/babel.config.js b/client/babel.config.js index e3a823c..6fa972e 100644 --- a/client/babel.config.js +++ b/client/babel.config.js @@ -2,6 +2,6 @@ module.exports = function (api) { api.cache(true); return { presets: ['babel-preset-expo'], - plugins: ['nativewind/babel'] + plugins: ['react-native-paper/babel', 'nativewind/babel'] }; }; diff --git a/client/components/Card.tsx b/client/components/Card.tsx index 167ba6c..527f00d 100644 --- a/client/components/Card.tsx +++ b/client/components/Card.tsx @@ -1,62 +1,27 @@ +import { styled } from 'nativewind'; import React from 'react'; -import { StyleSheet, View, GestureResponderEvent, Text } from 'react-native'; import { Card } from 'react-native-paper'; -import { useNavigation } from '@react-navigation/native'; interface ClickableCardProps { - med: Medication[]; + title: string; onPress: () => void; - children: JSX.Element[] | JSX.Element; - cardStyle?: object; - navigateTo?: string; + children?: JSX.Element[] | JSX.Element; } -const ClickableCard: React.FC = ({ - med, +const StyledModal = styled(Card.Title, { + props: { + titleStyle: true + } +}); + +export const ClickableCard: React.FC = ({ + title, onPress, - children, - cardStyle, - navigateTo + children }) => { - // const navigation = useNavigation(); - - const handlePress = () => { - if (navigateTo) { - console.log('trying to navigate!'); - // navigation.navigate(navigateTo as never); - } else { - onPress(); - } - }; - - const styles = StyleSheet.create({ - card: { - margin: 10, - width: 200, - borderRadius: 8, - borderWidth: 1, - borderColor: 'gray', - backgroundColor: 'lightblue', - shadowColor: '#000', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.3, - shadowRadius: 3, - elevation: 5 - }, - title: { - fontSize: 18, - marginBottom: 8, - fontWeight: 'bold' - }, - content: { - fontSize: 16, - color: 'gray' - } - }); - return ( - - + + {children} ); diff --git a/client/components/PopupModal.tsx b/client/components/PopupModal.tsx index aab5978..97f96df 100644 --- a/client/components/PopupModal.tsx +++ b/client/components/PopupModal.tsx @@ -1,56 +1,34 @@ +import { styled } from 'nativewind'; import React from 'react'; -import { - Modal, - Portal, - Text, - Button, - Provider as PaperProvider -} from 'react-native-paper'; +import { Modal, Portal } from 'react-native-paper'; interface PopupModalProps { - med: Medication[]; - onPress: () => void; - buttonStyle?: object; - modalStyle?: object; - modalContent?: React.ReactNode; + isVisible: boolean; + setVisible: (val: boolean) => void; + children?: JSX.Element[] | JSX.Element; } -const PopupModal: React.FC = ({ - med, - onPress, - buttonStyle, - modalStyle, - modalContent -}) => { - const [visible, setVisible] = React.useState(false); - - const showModal = () => setVisible(true); - const hideModal = () => setVisible(false); - const containerStyle = { - backgroundColor: 'white', - padding: 20, - ...modalStyle - }; +// rnp requires contentcontainerstyle to style the component, this will integrate native-wind into that +const StyledModal = styled(Modal, { + props: { + contentContainerStyle: true + } +}); +export default function PopupModal({ + children, + isVisible, + setVisible +}: PopupModalProps) { return ( - - - - {modalContent || Default Modal Content} - - - - + {children} + + ); -}; - -export default PopupModal; +} diff --git a/client/contexts/CareWalletContext.tsx b/client/contexts/CareWalletContext.tsx new file mode 100644 index 0000000..55347ca --- /dev/null +++ b/client/contexts/CareWalletContext.tsx @@ -0,0 +1,53 @@ +import React, { createContext, useContext, useEffect, useState } from 'react'; +import { getAuth, onAuthStateChanged } from 'firebase/auth'; +import { Group, User } from './types'; + +type CareWalletContextData = { + user: User; + group: Group; +}; + +const CareWalletContext = createContext({} as CareWalletContextData); + +export default function CareWalletProvider({ children }: { children: any }) { + const [user, setUser] = useState({} as User); + const [group, setGroup] = useState({} as Group); + const auth = getAuth(); + + useEffect(() => { + onAuthStateChanged(auth, (user) => { + const signedInUser: User = { + userID: user?.uid ?? '', + userEmail: user?.email ?? '' + }; + setUser(signedInUser); + }); + setGroup({ + groupID: 'TEMP - REPLACE WITH ACTUAL', + role: 'TEMP - REPLACE WITH ACTUAL' + }); + }, []); + + const CareWalletContextStore: CareWalletContextData = { + user: user, + group: group + }; + + return ( + + {children} + + ); +} + +export const useCareWalletContext = (): CareWalletContextData => { + const context = useContext(CareWalletContext); + + if (!context) { + throw new Error( + 'useCareWalletContext must be used within a CareWalletContextProvider' + ); + } + + return context; +}; diff --git a/client/contexts/types.ts b/client/contexts/types.ts new file mode 100644 index 0000000..82969df --- /dev/null +++ b/client/contexts/types.ts @@ -0,0 +1,9 @@ +export interface User { + userID: string; + userEmail: string; +} + +export interface Group { + groupID: string; + role: string; // TODO: update to enum +} diff --git a/client/navigation/AppNavigation.tsx b/client/navigation/AppNavigation.tsx new file mode 100644 index 0000000..fe7531f --- /dev/null +++ b/client/navigation/AppNavigation.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { NavigationProp } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import LoginPage from '../screens/Login'; +import AppStackBottomTabNavigator from './AppStackBottomTabNavigator'; + +export type AppScreenNames = ['MainNavScreens', 'Landing', 'Login']; +type AppStackParamList = Record; + +export type AppStackNavigation = NavigationProp; +const AppStack = createNativeStackNavigator(); + +export default function AppNavigation() { + return ( + + + + + ); +} diff --git a/client/navigation/AppStackBottomTabNavigator.tsx b/client/navigation/AppStackBottomTabNavigator.tsx new file mode 100644 index 0000000..3e7dd99 --- /dev/null +++ b/client/navigation/AppStackBottomTabNavigator.tsx @@ -0,0 +1,23 @@ +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import React from 'react'; +import MedList from '../screens/Medication'; +import { Text } from 'react-native'; +import Home from '../assets/home.svg'; + +const AppStackBottomTab = createBottomTabNavigator(); + +export default function AppStackBottomTabNavigator() { + return ( + + , + tabBarLabel: () => Landing + }} + component={MedList} + /> + + ); +} diff --git a/client/navigation/Router.tsx b/client/navigation/Router.tsx new file mode 100644 index 0000000..a1c1257 --- /dev/null +++ b/client/navigation/Router.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { NavigationContainer } from '@react-navigation/native'; +import AppNavigation from './AppNavigation'; + +export default function Router() { + return ( + + + + ); +} diff --git a/client/screens/Login.tsx b/client/screens/Login.tsx index 622f17f..71d8cf5 100644 --- a/client/screens/Login.tsx +++ b/client/screens/Login.tsx @@ -1,19 +1,15 @@ import React, { useState } from 'react'; -import { View, TextInput, Button, Alert, StyleSheet } from 'react-native'; +import { View, TextInput, Button, Alert } from 'react-native'; import { logIn } from '../services/auth/login'; import { signUp } from '../services/auth/signup'; -import { - useNavigation, - StackActions, - useRoute -} from '@react-navigation/native'; +import { useNavigation } from '@react-navigation/native'; +import { AppStackNavigation } from '../navigation/AppNavigation'; const LoginPage: React.FC = () => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); - const navigation = useNavigation(); - const route = useRoute(); + const navigation = useNavigation(); const handleLogin = async () => { if (!email || !password) { @@ -26,7 +22,7 @@ const LoginPage: React.FC = () => { } else { Alert.alert('Login Success', 'Welcome back!'); // console.log('result: ', result); - navigation.navigate('BottomNav'); + navigation.navigate('MainNavScreens'); } }; @@ -41,7 +37,7 @@ const LoginPage: React.FC = () => { } else { Alert.alert('Signup Success', 'Welcome to the app!'); // console.log('result: ', result); - navigation.navigate('BottomNav'); + navigation.navigate('MainNavScreens'); } }; diff --git a/client/screens/Medication.tsx b/client/screens/Medication.tsx index d412928..0ddfbc0 100644 --- a/client/screens/Medication.tsx +++ b/client/screens/Medication.tsx @@ -1,22 +1,51 @@ import * as React from 'react'; -import { View, Text } from 'react-native'; +import { View, Text, ScrollView } from 'react-native'; import { getAllMedications } from '../services/medication'; import { Medication } from '../types/medication'; +import { useCareWalletContext } from '../contexts/CareWalletContext'; +import ClickableCard from '../components/Card'; +import PopupModal from '../components/PopupModal'; export default function MedList() { const [medications, setMedications] = React.useState(); + const [selectedMed, setSelectedMed] = React.useState(); + const { user, group } = useCareWalletContext(); + const [visible, setVisible] = React.useState(false); React.useEffect(() => { getAllMedications().then((med) => setMedications(med)); }, []); return ( - {medications && - medications.map((med, index) => ( - - {`Name: ${med.medication_name} id: ${med.medication_id}`} - - ))} + + + {selectedMed?.medication_name} + + ID: {selectedMed?.medication_id} + + + {medications && + medications.map((med, index) => ( + { + setSelectedMed(med); + setVisible(true); + }} + > + ID: {med.medication_id} + + ))} + + {user && group && ( + + The user id is: {user.userID} + The user email is: {user.userEmail} + The group id is: {group.groupID} + The group role is: {group.role} + + )} ); } diff --git a/client/tailwind.config.js b/client/tailwind.config.js index 0e7790f..2a6c6f1 100644 --- a/client/tailwind.config.js +++ b/client/tailwind.config.js @@ -1,6 +1,11 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - content: ['./App.{js,jsx,ts,tsx}', './screens/**/*.{js,jsx,ts,tsx}'], + content: [ + './App.{js,jsx,ts,tsx}', + './screens/**/*.{js,jsx,ts,tsx}', + './components/**/*.{js,jsx,ts,tsx}', + './navigation/**/*.{js,jsx,ts,tsx}' + ], theme: { extend: {} },