diff --git a/.github/workflows/CI.yml b/.github/workflows/BackendCI.yml
similarity index 65%
rename from .github/workflows/CI.yml
rename to .github/workflows/BackendCI.yml
index 50c64a2..66c3ee5 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/BackendCI.yml
@@ -1,10 +1,16 @@
-name: CI (Formatting/Linting, Testing, Building)
+name: Backend CI
on:
push:
- branches: ['main']
+ paths:
+ - "backend/**"
+ - ".github/**"
+ branches: ["main"]
pull_request:
- branches: ['main']
+ paths:
+ - "backend/**"
+ - ".github/**"
+ branches: ["main"]
jobs:
lint:
@@ -23,25 +29,8 @@ jobs:
with:
go-version: ${{ matrix.go-version }}
- - name: Set up Node
- uses: actions/setup-node@v2
- with:
- node-version: ${{ matrix.node-version }}
-
- - name: Install Task
- uses: arduino/setup-task@v1
- with:
- version: 3.x
- repo-token: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Format Frontend
- run: |
- task build
- cd client
- npm run check
- name: Format Backend
run: |
- task build
cd backend
be_format_output=$(go fmt ./...)
if [[ -n "$be_format_output" ]]; then
@@ -51,7 +40,6 @@ jobs:
fi
- name: Lint Backend
run: |
- task build
cd backend
be_lint_output=$(go vet ./...)
if [[ -n "$be_lint_output" ]]; then
@@ -90,8 +78,7 @@ jobs:
uses: actions/checkout@v4
- name: Import DB seed data
- run:
- psql -d postgresql://user:pwd@172.17.0.1:5432/carewallet -f init.sql
+ run: psql -d postgresql://user:pwd@172.17.0.1:5432/carewallet -f init.sql
working-directory: ./backend/db/migrations
- name: Set up Go
@@ -99,16 +86,11 @@ jobs:
with:
go-version: ${{ matrix.go-version }}
- - name: Install Task
- uses: arduino/setup-task@v1
- with:
- version: 3.x
- repo-token: ${{ secrets.GITHUB_TOKEN }}
-
- name: Run Go tests
run: |
- task build
- task test-all
+ cd backend
+ go mod tidy
+ go test -count=1 carewallet/...
build:
name: Build
@@ -126,17 +108,7 @@ jobs:
with:
go-version: ${{ matrix.go-version }}
- - name: Set up Node
- uses: actions/setup-node@v2
- with:
- node-version: ${{ matrix.node-version }}
-
- - name: Install Task
- uses: arduino/setup-task@v1
- with:
- version: 3.x
- repo-token: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Build app
+ - name: Build App
run: |
- task build
+ cd backend
+ go mod tidy
diff --git a/.github/workflows/FrontendCI.yml b/.github/workflows/FrontendCI.yml
new file mode 100644
index 0000000..401d5e6
--- /dev/null
+++ b/.github/workflows/FrontendCI.yml
@@ -0,0 +1,78 @@
+name: Frontend CI
+
+on:
+ push:
+ paths:
+ - "client/**"
+ - ".github/**"
+ branches: ["main"]
+ pull_request:
+ paths:
+ - "client/**"
+ - ".github/**"
+ branches: ["main"]
+
+jobs:
+ format:
+ name: Format
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ node-version: [20.x]
+ go-version: [1.21.x]
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Set up Node
+ uses: actions/setup-node@v2
+ with:
+ node-version: ${{ matrix.node-version }}
+
+ - name: Format Frontend
+ run: |
+ cd client
+ npm install prettier
+ npx prettier --check .
+ lint:
+ name: Lint
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ node-version: [20.x]
+ go-version: [1.21.x]
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Set up Node
+ uses: actions/setup-node@v2
+ with:
+ node-version: ${{ matrix.node-version }}
+
+ - name: Lint Frontend
+ run: |
+ cd client
+ npm install
+ npm run lint
+
+ build:
+ name: Build
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ node-version: [20.x]
+ go-version: [1.21.x]
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Set up Node
+ uses: actions/setup-node@v2
+ with:
+ node-version: ${{ matrix.node-version }}
+
+ - name: Build App
+ run: |
+ cd client
+ npm install
diff --git a/.prettierrc b/.prettierrc
deleted file mode 100644
index 77991e0..0000000
--- a/.prettierrc
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "semi": true,
- "trailingComma": "none",
- "singleQuote": true,
- "printWidth": 80,
- "tabWidth": 2,
- "endOfLine": "auto",
- "bracketSpacing": true,
- "proseWrap": "always"
-}
diff --git a/README.md b/README.md
index 4be88b8..17c35e9 100644
--- a/README.md
+++ b/README.md
@@ -1,119 +1,89 @@
Care-Wallet
-
- A fullstack application for the Care-Wallet project
-
-
-
-
-
+
+
+
+
+
+
+ A fullstack application for the Care-Wallet project
+
-## Set Up Your Development Environment
+## Stack
+
+![Go](https://img.shields.io/badge/go-%2300ADD8.svg?style=for-the-badge&logo=go&logoColor=white)
+![Postgres](https://img.shields.io/badge/postgres-%23316192.svg?style=for-the-badge&logo=postgresql&logoColor=white)
+
+![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white)
+![React Native](https://img.shields.io/badge/react_native-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB)
+![TailwindCSS](https://img.shields.io/badge/tailwindcss-%2338B2AC.svg?style=for-the-badge&logo=tailwind-css&logoColor=white)
-First, understand the tech stack:
+## Tools
-- The database is [PostGreSQL](https://www.postgresql.org/) and will be
- containerized using [Docker](https://www.docker.com/).
-- The backend is [Golang](https://go.dev/)
-- The frontend is [TypeScript](https://www.typescriptlang.org/) with
- [ReactNative](https://reactnative.dev/) and
- [NativeWind](https://www.nativewind.dev) and uses [Expo](https://expo.dev/) as
- a build tool
+![Expo](https://img.shields.io/badge/expo-1C1E24?style=for-the-badge&logo=expo&logoColor=#D04A37)
+![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white)
+![Swagger](https://img.shields.io/badge/-Swagger-%23Clojure?style=for-the-badge&logo=swagger&logoColor=white)
+
+## Development Enviroment Setup
Before compiling and running our application, we need to install/setup several
languages, package managers, and various tools. The installation process can
vary, so follow the instructions for each item below!
-- [Go](https://go.dev/doc/install) - our primary backend language
-- [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) - our
- package manager in the frontend
-- [Docker Desktop](https://www.docker.com/products/docker-desktop/) - useful for
- running docker
-- [ngrok](https://ngrok.com/docs/getting-started/) - Allows us to easily connect
- the frontend to backend code
-- [expo-go](https://docs.expo.dev/get-started/expo-go/) - Expo allows mobile
- devices to scan a QR code and view the code running on a mobile device
-
-If you wish to simulate a phone from your computer either below will work:
-
-- [xcode](https://docs.expo.dev/workflow/ios-simulator/) - A simulator to view
- the code on an iphone from a laptop
-- [android studio](https://docs.expo.dev/workflow/android-studio-emulator/) - An
- emulator to view the code on an android device from a laptop
-
-If everything was successful, you can now compile and run the project!
-
-Next, understand the development tools that will make our lives easier:
-
-- [Pre-commit](https://pre-commit.com) - standardizing code style and commits
-- [Swagger](https://github.com/swaggo/swag) - visualizing the api and return
- types of requests from the database
-- [Task](https://taskfile.dev) - speeding up development by running long
- commands in quick phrases
-
-Before committing anything, we need to install several tools. The installation
-process can vary, so follow the instructions for each item below!
-
-- [pre-commit](https://pre-commit.com/#installation) - our development tool to
- standardize development and ensure every file follows the same styling
- guidelines.
-- [commitizen](https://commitizen-tools.github.io/commitizen/#installation) -
- This allows us to organize commits! By typing `cz c` instead of
- `git commit -m ""`, we can organize each of our commits into one of nine
- categories:
- - **fix**: A bug fix. Correlates with PATCH in SemVer
- - **feat**: A new feature. Correlates with MINOR in SemVer
- - **docs**: Documentation only changes
- - **style**: Changes that do not affect the meaning of the code (white-space,
- formatting, missing semi-colons, etc)
- - **refactor**: A code change that neither fixes a bug nor adds a feature
- - **perf**: A code change that improves performance
- - **test**: Adding missing or correcting existing tests
- - **build**: Changes that affect the build system or external dependencies
- (example scopes: pip, docker, npm)
- - **ci**: Changes to our CI configuration files and scripts (example scopes:
- GitLabCI)
-- [Task](https://taskfile.dev/installation/) - our development tool to quickly
- run commands that run, test, and clean files.
-
-## Extra Dependencies
-
-Install these into the backend directory
-
-1. go install github.com/swaggo/swag/cmd/swag@latest
+[Go](https://go.dev/doc/install) our primary backend language.
-## Before Running
+[Node Package Manager](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
+our package manager in the frontend.
-1. Create a .env file in the root directory with a single line:
- `EXPO_PUBLIC_API_DOMAIN=your-ngrok-static-domain-here`
- - this will be used by the frontend services as well as the task file to
- launch ngrok!
+[Docker](https://www.docker.com/get-started/) and
+[Docker Desktop](https://www.docker.com/products/docker-desktop/) our Postgres
+Database will be containerized in Docker.
-## Running the project
+[Ngrok](https://ngrok.com/docs/getting-started/) Allows us to easily connect the
+frontend to backend code.
-1. Launch Docker Desktop
-2. In the base of the repo: run `task start-docker`
+[Swagger](https://github.com/swaggo/swag) visualizing the api and return types
+of requests from the database.
- - This will run `docker-compose down` then `docker-compose up`
+[Task](https://taskfile.dev) speeding up development by running long commands in
+quick phrases.
-3. To build all of the dependencies of the project: run `task build`
+[Nodemon](https://www.npmjs.com/package/nodemon) a tool that watches code and
+reloads the build if it sees changes.
- - This will install both frontend and backend dependencies
+## Before Running
-4. Then, open a new tab to run commands in: run `task start-backend`
+Create an .env file in the root directory:
- - This will generate the swagger docs as well as start the backend
- - You can now view swagger: http://localhost:8080/swagger/index.html
+```
+ EXPO_PUBLIC_API_DOMAIN=your-ngrok-static-domain-here
+ AWS_ACCESS_KEY=your-aws-access-key-here
+ AWS_SECRET_KEY=your-aws-secret-key-here
+```
-5. Next, in a new tab run `task start-ngrok`
+## Before Contributing
-6. Finally, open one last new tab: run `task start-frontend`
+Before contributing to the project, we need to install/setup several various
+tools. The installation process can vary, so follow the instructions for each
+item below!
- - This will start the frontend
+[Pre-commit](https://pre-commit.com) standardizing code style and commits
-7. From here follow the prompt in step 6 to launch the frontend on your device
- of choice
+[Commitizen](https://commitizen-tools.github.io/commitizen/) organizing our
+commits into categories
+
+## Running The Project
+
+1. Launch Docker Desktop
+2. In the base of the repo: run `task start-docker`
+3. Then, open a new tab to run commands in: run `task start-backend` or
+ `task start-dev`
+ - You can now view swagger: http://localhost:8080/swagger/index.html
+4. Next, in a new tab run `task start-ngrok`
+5. Finally, open one last new tab: run `task start-frontend`
diff --git a/Taskfile.yaml b/Taskfile.yaml
index 4ba4c60..b0f0c44 100644
--- a/Taskfile.yaml
+++ b/Taskfile.yaml
@@ -1,5 +1,5 @@
-version: '3'
-dotenv: ['.env']
+version: "3"
+dotenv: [".env"]
tasks:
build:
- |
@@ -13,11 +13,14 @@ tasks:
echo -e "Frontend Formatting..."
cd client
npm run format
+ echo -e "\nFrontend Linting...\n"
+ npm run lint:fix
echo -e "\nBackend Formatting...\n"
cd ../backend
go fmt ./...
- go vet ./...
swag f
+ echo -e "\nBackend Linting...\n"
+ go vet ./...
start-dev:
- |
@@ -36,7 +39,7 @@ tasks:
npm start
start-ngrok:
- dotenv: ['.env']
+ dotenv: [".env"]
cmds:
- |
cd backend
diff --git a/backend/db/migrations/init.sql b/backend/db/migrations/init.sql
index dd0d026..e57ba99 100644
--- a/backend/db/migrations/init.sql
+++ b/backend/db/migrations/init.sql
@@ -129,7 +129,8 @@ VALUES
('Smith Family', NOW()),
('Johnson Support Network', NOW()),
('Williams Care Team', NOW()),
- ('Brown Medical Group', NOW())
+ ('Brown Medical Group', NOW()),
+ ('Care-Wallet Group', NOW()),
;
INSERT INTO users (user_id, first_name, last_name, email, phone, address)
@@ -137,7 +138,8 @@ VALUES
('user1', 'John', 'Smith', 'john.smith@example.com', '123-456-7890', '123 Main St'),
('user2', 'Jane', 'Doe', 'jane.doe@example.com', '987-654-3210', '456 Elm St'),
('user3', 'Bob', 'Johnson', 'bob.johnson@example.com', NULL, NULL),
- ('user4', 'Emily', 'Garcia', 'emily.garcia@example.com', '555-1212', '789 Oak Ave')
+ ('user4', 'Emily', 'Garcia', 'emily.garcia@example.com', '555-1212', '789 Oak Ave'),
+ ('fIoFY26mJnYWH8sNdfuVoxpnVnr1', 'Matt', 'McCoy', '', '', '');
;
INSERT INTO group_roles (group_id, user_id, role)
@@ -148,7 +150,8 @@ VALUES
(2, 'user4', 'SECONDARY'),
(3, 'user4', 'PATIENT'),
(4, 'user1', 'SECONDARY'),
- (4, 'user3', 'SECONDARY')
+ (4, 'user3', 'SECONDARY'),
+ (5, 'fIoFY26mJnYWH8sNdfuVoxpnVnr1', 'PRIMARY')
;
INSERT INTO task (group_id, created_by, created_date, start_date, end_date, notes, task_status, task_type)
diff --git a/backend/docs/docs.go b/backend/docs/docs.go
index 7579b6b..52d453c 100644
--- a/backend/docs/docs.go
+++ b/backend/docs/docs.go
@@ -29,6 +29,20 @@ const docTemplate = `{
"name": "file_data",
"in": "formData",
"required": true
+ },
+ {
+ "type": "string",
+ "description": "The userId of the uploader",
+ "name": "upload_by",
+ "in": "formData",
+ "required": true
+ },
+ {
+ "type": "integer",
+ "description": "The groupId of the uploader",
+ "name": "group_id",
+ "in": "formData",
+ "required": true
}
],
"responses": {
@@ -39,7 +53,10 @@ const docTemplate = `{
}
},
"400": {
- "description": "Bad Request"
+ "description": "Bad Request",
+ "schema": {
+ "type": "string"
+ }
}
}
}
@@ -135,6 +152,38 @@ const docTemplate = `{
}
}
}
+ },
+ "post": {
+ "description": "add a medication to a users medlist",
+ "tags": [
+ "medications"
+ ],
+ "summary": "add a medication",
+ "parameters": [
+ {
+ "description": "a medication",
+ "name": "_",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/models.Medication"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/models.Medication"
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
}
},
"/tasks/assigned": {
@@ -355,7 +404,7 @@ const docTemplate = `{
"type": "integer"
},
"upload_by": {
- "type": "integer"
+ "type": "string"
},
"upload_date": {
"type": "string"
diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json
index 376e99d..1dd62dc 100644
--- a/backend/docs/swagger.json
+++ b/backend/docs/swagger.json
@@ -22,6 +22,20 @@
"name": "file_data",
"in": "formData",
"required": true
+ },
+ {
+ "type": "string",
+ "description": "The userId of the uploader",
+ "name": "upload_by",
+ "in": "formData",
+ "required": true
+ },
+ {
+ "type": "integer",
+ "description": "The groupId of the uploader",
+ "name": "group_id",
+ "in": "formData",
+ "required": true
}
],
"responses": {
@@ -32,7 +46,10 @@
}
},
"400": {
- "description": "Bad Request"
+ "description": "Bad Request",
+ "schema": {
+ "type": "string"
+ }
}
}
}
@@ -128,6 +145,38 @@
}
}
}
+ },
+ "post": {
+ "description": "add a medication to a users medlist",
+ "tags": [
+ "medications"
+ ],
+ "summary": "add a medication",
+ "parameters": [
+ {
+ "description": "a medication",
+ "name": "_",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/models.Medication"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/models.Medication"
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
}
},
"/tasks/assigned": {
@@ -348,7 +397,7 @@
"type": "integer"
},
"upload_by": {
- "type": "integer"
+ "type": "string"
},
"upload_date": {
"type": "string"
diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml
index 5b3ec14..eca5625 100644
--- a/backend/docs/swagger.yaml
+++ b/backend/docs/swagger.yaml
@@ -22,7 +22,7 @@ definitions:
task_id:
type: integer
upload_by:
- type: integer
+ type: string
upload_date:
type: string
type: object
@@ -110,6 +110,16 @@ paths:
name: file_data
required: true
type: file
+ - description: The userId of the uploader
+ in: formData
+ name: upload_by
+ required: true
+ type: string
+ - description: The groupId of the uploader
+ in: formData
+ name: group_id
+ required: true
+ type: integer
responses:
"200":
description: OK
@@ -117,6 +127,8 @@ paths:
$ref: '#/definitions/models.File'
"400":
description: Bad Request
+ schema:
+ type: string
summary: Upload a file
tags:
- file
@@ -181,6 +193,27 @@ paths:
summary: Get All Meds
tags:
- medications
+ post:
+ description: add a medication to a users medlist
+ parameters:
+ - description: a medication
+ in: body
+ name: _
+ required: true
+ schema:
+ $ref: '#/definitions/models.Medication'
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.Medication'
+ "400":
+ description: Bad Request
+ schema:
+ type: string
+ summary: add a medication
+ tags:
+ - medications
/tasks/{tid}/assign:
post:
description: assign users to task
diff --git a/backend/models/file.go b/backend/models/file.go
index 5f66fab..b0878e4 100644
--- a/backend/models/file.go
+++ b/backend/models/file.go
@@ -4,7 +4,7 @@ type File struct {
FileID int `json:"file_id"`
FileName string `json:"file_name"`
GroupID int `json:"group_id"`
- UploadBy int `json:"upload_by"`
+ UploadBy string `json:"upload_by"`
UploadDate string `json:"upload_date"`
FileSize int64 `json:"file_size"`
TaskID int `json:"task_id"`
diff --git a/backend/schema/files/routes.go b/backend/schema/files/routes.go
index 0507466..fe98610 100644
--- a/backend/schema/files/routes.go
+++ b/backend/schema/files/routes.go
@@ -17,35 +17,28 @@ func FileGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup {
files := v1.Group("files")
{
- files.POST("/upload", c.UploadFileRoute)
+ files.POST("/upload", c.UploadFile)
}
return files
}
-// GetFiles godoc
+// UploadFile godoc
//
// @summary Upload a file
// @description Upload a file to database and S3 bucket
// @tags file
+//
// @param file_data formData file true "Body with file zip"
+// @param upload_by formData string true "The userId of the uploader"
+// @param group_id formData int true "The groupId of the uploader"
//
// @success 200 {object} models.File
-// @failure 400
+// @failure 400 {object} string
// @router /files/upload [post]
-func (pg *PgModel) UploadFileRoute(c *gin.Context) {
- // TODO: Ensure Swagger Knows about the bad request returns!!!
+func (pg *PgModel) UploadFile(c *gin.Context) {
var file models.File
- if err := c.Bind(&file); err != nil {
- c.JSON(http.StatusBadRequest, "Failed to process the request")
- return
- }
- userID := c.GetHeader("user_id")
- groupID := c.GetHeader("group_id")
- file.UploadBy, _ = strconv.Atoi(userID)
- file.GroupID, _ = strconv.Atoi(groupID)
-
form, err := c.MultipartForm()
if err != nil {
c.JSON(http.StatusBadRequest, "Failed to get form")
@@ -53,6 +46,14 @@ func (pg *PgModel) UploadFileRoute(c *gin.Context) {
}
fileResponse := form.File["file_data"][0]
+ file.UploadBy = form.Value["upload_by"][0]
+ file.GroupID, err = strconv.Atoi(form.Value["group_id"][0])
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, "Failed to parse groupid")
+ return
+ }
+
fileData, err := fileResponse.Open()
if err != nil {
c.JSON(http.StatusBadRequest, "Failed to open file")
diff --git a/backend/schema/medication/routes.go b/backend/schema/medication/routes.go
index 5f7f3f0..885274e 100644
--- a/backend/schema/medication/routes.go
+++ b/backend/schema/medication/routes.go
@@ -1,6 +1,7 @@
package medication
import (
+ "carewallet/models"
"net/http"
"github.com/gin-gonic/gin"
@@ -16,6 +17,7 @@ func GetMedicationGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup {
medications := v1.Group("medications")
{
medications.GET("", c.GetMedications)
+ medications.POST("", c.AddMedications)
}
return medications
@@ -37,3 +39,28 @@ func (pg *PgModel) GetMedications(c *gin.Context) {
c.JSON(http.StatusOK, med)
}
+
+// AddMedications godoc
+//
+// @summary add a medication
+// @description add a medication to a users medlist
+// @tags medications
+//
+// @param _ body models.Medication true "a medication"
+//
+// @success 200 {object} models.Medication
+// @failure 400 {object} string
+// @router /medications [post]
+func (pg *PgModel) AddMedications(c *gin.Context) {
+ var medbody models.Medication
+ c.Bind(&medbody)
+
+ med, err := AddMedToDB(pg.Conn, medbody)
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+
+ c.JSON(http.StatusOK, med)
+}
diff --git a/backend/schema/medication/transactions.go b/backend/schema/medication/transactions.go
index a90471d..1416f0e 100644
--- a/backend/schema/medication/transactions.go
+++ b/backend/schema/medication/transactions.go
@@ -34,3 +34,15 @@ func GetAllMedsFromDB(pool *pgx.Conn) ([]models.Medication, error) {
return results, nil
}
+
+func AddMedToDB(pool *pgx.Conn, med models.Medication) (models.Medication, error) {
+ err := pool.QueryRow("INSERT INTO medication (medication_id, medication_name) VALUES ($1, $2) RETURNING medication_id;",
+ med.MedicationID, med.MedicationName).Scan(&med.MedicationID)
+
+ if err != nil {
+ print(err.Error())
+ return models.Medication{}, err
+ }
+
+ return med, nil
+}
diff --git a/client/.eslintrc b/client/.eslintrc
new file mode 100644
index 0000000..1f85151
--- /dev/null
+++ b/client/.eslintrc
@@ -0,0 +1,70 @@
+{
+ "parser": "@typescript-eslint/parser",
+ "parserOptions": {
+ "ecmaVersion": 2018,
+ "sourceType": "module"
+ },
+ "env": {
+ "node": true
+ },
+ "plugins": [
+ "@typescript-eslint",
+ "prettier",
+ "import",
+ "react",
+ "eslint-plugin-filenames"
+ ],
+ "extends": [
+ "eslint:recommended",
+ "plugin:@typescript-eslint/recommended",
+ "plugin:import/recommended",
+ "plugin:import/typescript",
+ "plugin:react/recommended",
+ "plugin:prettier/recommended"
+ ],
+ "rules": {
+ // Enforces function for components
+ "react/function-component-definition": [
+ "error",
+ {
+ "namedComponents": "function-declaration",
+ "unnamedComponents": "arrow-function"
+ }
+ ],
+ // Enforces no * imports
+ "no-restricted-syntax": [
+ "error",
+ {
+ "selector": ":matches(ImportNamespaceSpecifier, ExportAllDeclaration, ExportNamespaceSpecifier)",
+ "message": "Import/export only modules you need"
+ }
+ ],
+ // Enforces no default export
+ "import/no-default-export": "error",
+ // Enforces react not being added to files where it isnt needed
+ "react/jsx-uses-react": "error",
+ // Enforces const, if a variable isnt reassigned
+ "prefer-const": "error",
+ // enforces preventing <>>
+ "react/jsx-no-useless-fragment": "error"
+ },
+ "overrides": [
+ {
+ "files": ["screens/**/*", "App.tsx", "declaration.d.ts"],
+ "rules": {
+ // Allows default exports for the above files
+ "import/no-default-export": "off"
+ }
+ }
+ ],
+ "settings": {
+ "import/resolver": {
+ "typescript": {
+ "project": "./tsconfig.json"
+ }
+ },
+ "react": {
+ "version": "detect"
+ }
+ }
+}
diff --git a/client/.prettierrc b/client/.prettierrc
new file mode 100644
index 0000000..1008d85
--- /dev/null
+++ b/client/.prettierrc
@@ -0,0 +1,23 @@
+{
+ "semi": true,
+ "trailingComma": "none",
+ "singleQuote": true,
+ "printWidth": 80,
+ "tabWidth": 2,
+ "endOfLine": "auto",
+ "bracketSpacing": true,
+ "proseWrap": "always",
+ "plugins": [
+ "@trivago/prettier-plugin-sort-imports",
+ "prettier-plugin-tailwindcss"
+ ],
+ "importOrder": [
+ "^(react|react-native)$",
+ "",
+ "",
+ "^[.]"
+ ],
+ "importOrderSeparation": true,
+ "importOrderSortSpecifiers": true,
+ "importOrderCaseInsensitive": true
+}
diff --git a/client/App.tsx b/client/App.tsx
index 23205c7..4ace8fe 100644
--- a/client/App.tsx
+++ b/client/App.tsx
@@ -1,17 +1,24 @@
-import * as React from 'react';
-import { SafeAreaView } from 'react-native-safe-area-context';
-import Router from './navigation/Router';
-import CareWalletProvider from './contexts/CareWalletContext';
+import React from 'react';
+
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { PaperProvider } from 'react-native-paper';
+import { SafeAreaView } from 'react-native-safe-area-context';
+
+import { CareWalletProvider } from './contexts/CareWalletContext';
+import { Router } from './navigation/Router';
export default function App() {
+ const queryClient = new QueryClient();
+
return (
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
);
}
diff --git a/client/components/Card.tsx b/client/components/Card.tsx
index 527f00d..27bdb82 100644
--- a/client/components/Card.tsx
+++ b/client/components/Card.tsx
@@ -1,5 +1,5 @@
-import { styled } from 'nativewind';
import React from 'react';
+
import { Card } from 'react-native-paper';
interface ClickableCardProps {
@@ -8,23 +8,15 @@ interface ClickableCardProps {
children?: JSX.Element[] | JSX.Element;
}
-const StyledModal = styled(Card.Title, {
- props: {
- titleStyle: true
- }
-});
-
-export const ClickableCard: React.FC = ({
+export function ClickableCard({
title,
onPress,
children
-}) => {
+}: ClickableCardProps) {
return (
-
+
{children}
);
-};
-
-export default ClickableCard;
+}
diff --git a/client/components/ClickableCard.tsx b/client/components/ClickableCard.tsx
new file mode 100644
index 0000000..ca94259
--- /dev/null
+++ b/client/components/ClickableCard.tsx
@@ -0,0 +1,22 @@
+import React from 'react';
+
+import { Card } from 'react-native-paper';
+
+interface ClickableCardProps {
+ title: string;
+ onPress: () => void;
+ children?: JSX.Element[] | JSX.Element;
+}
+
+export function ClickableCard({
+ title,
+ onPress,
+ children
+}: ClickableCardProps) {
+ return (
+
+
+ {children}
+
+ );
+}
diff --git a/client/components/DocPickerButton.tsx b/client/components/DocPickerButton.tsx
index bc9295b..37e80d3 100644
--- a/client/components/DocPickerButton.tsx
+++ b/client/components/DocPickerButton.tsx
@@ -1,34 +1,42 @@
-import React, { useState } from 'react';
-import { View, Button, Text } from 'react-native';
-import * as DocumentPicker from 'expo-document-picker';
-import { uploadFile } from '../services/file';
+import React from 'react';
+import { Pressable, Text } from 'react-native';
-export default function DocPickerButton() {
- const [pickedDocument, setPickedDocument] = useState(null);
+import { getDocumentAsync } from 'expo-document-picker';
+
+import { useCareWalletContext } from '../contexts/CareWalletContext';
+import { useFile } from '../services/file';
+
+export function DocPickerButton() {
+ const { user, group } = useCareWalletContext();
+ const { uploadFileMutation } = useFile();
const pickDocument = async () => {
try {
- const result = await DocumentPicker.getDocumentAsync({
+ await getDocumentAsync({
type: '*/*',
copyToCacheDirectory: false
+ }).then((res) => {
+ if (!res.canceled) {
+ uploadFileMutation({
+ file: res.assets[0],
+ userId: user.userID,
+ groupId: group.groupID
+ });
+ }
});
- console.log('result', result);
-
- if (result.canceled === false) {
- // TODO get userID and groupID
- const userID = 0;
- const groupID = 0;
- await uploadFile(result.assets[0], userID, groupID);
- }
} catch (err) {
console.log('err', err);
}
};
return (
-
-
- {pickedDocument && Picked Document: {pickedDocument}}
-
+
+
+ Pick Document
+
+
);
}
diff --git a/client/components/PopupModal.tsx b/client/components/PopupModal.tsx
index 97f96df..a5302fd 100644
--- a/client/components/PopupModal.tsx
+++ b/client/components/PopupModal.tsx
@@ -1,5 +1,6 @@
-import { styled } from 'nativewind';
import React from 'react';
+
+import { styled } from 'nativewind';
import { Modal, Portal } from 'react-native-paper';
interface PopupModalProps {
@@ -8,14 +9,13 @@ interface PopupModalProps {
children?: JSX.Element[] | JSX.Element;
}
-// 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({
+export function PopupModal({
children,
isVisible,
setVisible
@@ -25,7 +25,7 @@ export default function PopupModal({
setVisible(false)}
- contentContainerStyle="border-10 rounded-3xl border-white w-[90%] h-[60%] self-center bg-white"
+ contentContainerStyle="border-10 rounded-3xl w-[90%] h-[60%] self-center bg-carewallet-white"
>
{children}
diff --git a/client/contexts/CareWalletContext.tsx b/client/contexts/CareWalletContext.tsx
index 55347ca..9170200 100644
--- a/client/contexts/CareWalletContext.tsx
+++ b/client/contexts/CareWalletContext.tsx
@@ -1,5 +1,7 @@
import React, { createContext, useContext, useEffect, useState } from 'react';
+
import { getAuth, onAuthStateChanged } from 'firebase/auth';
+
import { Group, User } from './types';
type CareWalletContextData = {
@@ -9,7 +11,11 @@ type CareWalletContextData = {
const CareWalletContext = createContext({} as CareWalletContextData);
-export default function CareWalletProvider({ children }: { children: any }) {
+export function CareWalletProvider({
+ children
+}: {
+ children: JSX.Element | JSX.Element[];
+}) {
const [user, setUser] = useState({} as User);
const [group, setGroup] = useState({} as Group);
const auth = getAuth();
@@ -20,11 +26,13 @@ export default function CareWalletProvider({ children }: { children: any }) {
userID: user?.uid ?? '',
userEmail: user?.email ?? ''
};
+
setUser(signedInUser);
- });
- setGroup({
- groupID: 'TEMP - REPLACE WITH ACTUAL',
- role: 'TEMP - REPLACE WITH ACTUAL'
+
+ setGroup({
+ groupID: 999,
+ role: 'TEMP'
+ });
});
}, []);
diff --git a/client/contexts/types.ts b/client/contexts/types.ts
index 82969df..81c0d92 100644
--- a/client/contexts/types.ts
+++ b/client/contexts/types.ts
@@ -4,6 +4,6 @@ export interface User {
}
export interface Group {
- groupID: string;
+ groupID: number;
role: string; // TODO: update to enum
}
diff --git a/client/types/declaration.d.ts b/client/declaration.d.ts
similarity index 99%
rename from client/types/declaration.d.ts
rename to client/declaration.d.ts
index fed8d16..fe78ee8 100644
--- a/client/types/declaration.d.ts
+++ b/client/declaration.d.ts
@@ -1,5 +1,6 @@
declare module '*.svg' {
import React from 'react';
+
import { SvgProps } from 'react-native-svg';
const content: React.FC;
export default content;
diff --git a/client/firebase.config.js b/client/firebase.config.js
index 9121b2e..7377340 100644
--- a/client/firebase.config.js
+++ b/client/firebase.config.js
@@ -1,7 +1,7 @@
// Import the functions you need from the SDKs you need
-import { initializeApp } from 'firebase/app';
-import { initializeAuth, getReactNativePersistence } from 'firebase/auth';
import ReactNativeAsyncStorage from '@react-native-async-storage/async-storage';
+import { initializeApp } from 'firebase/app';
+import { getReactNativePersistence, initializeAuth } from 'firebase/auth';
// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
diff --git a/client/navigation/AppNavigation.tsx b/client/navigation/AppNavigation.tsx
index fe7531f..945797f 100644
--- a/client/navigation/AppNavigation.tsx
+++ b/client/navigation/AppNavigation.tsx
@@ -1,16 +1,22 @@
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;
+import LoginPage from '../screens/LoginPage';
+import { AppStackBottomTabNavigator } from './AppStackBottomTabNavigator';
+
+export type AppStackParamList = {
+ Main: undefined;
+ Home: undefined;
+ Login: undefined;
+};
export type AppStackNavigation = NavigationProp;
+
const AppStack = createNativeStackNavigator();
-export default function AppNavigation() {
+export function AppNavigation() {
return (
diff --git a/client/navigation/AppStackBottomTabNavigator.tsx b/client/navigation/AppStackBottomTabNavigator.tsx
index 3e7dd99..3df330f 100644
--- a/client/navigation/AppStackBottomTabNavigator.tsx
+++ b/client/navigation/AppStackBottomTabNavigator.tsx
@@ -1,22 +1,24 @@
-import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import React from 'react';
-import MedList from '../screens/Medication';
import { Text } from 'react-native';
+
+import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
+
import Home from '../assets/home.svg';
+import MedicationList from '../screens/MedicationList';
const AppStackBottomTab = createBottomTabNavigator();
-export default function AppStackBottomTabNavigator() {
+export function AppStackBottomTabNavigator() {
return (
,
+ tabBarIcon: () => ,
tabBarLabel: () => Landing
}}
- component={MedList}
+ component={MedicationList}
/>
);
diff --git a/client/navigation/Router.tsx b/client/navigation/Router.tsx
index a1c1257..46bee53 100644
--- a/client/navigation/Router.tsx
+++ b/client/navigation/Router.tsx
@@ -1,8 +1,10 @@
import React from 'react';
+
import { NavigationContainer } from '@react-navigation/native';
-import AppNavigation from './AppNavigation';
-export default function Router() {
+import { AppNavigation } from './AppNavigation';
+
+export function Router() {
return (
diff --git a/client/package.json b/client/package.json
index 33dd1c9..4eba5b0 100644
--- a/client/package.json
+++ b/client/package.json
@@ -9,20 +9,19 @@
"ios": "expo start --ios",
"web": "expo start --web",
"format": "prettier --write .",
- "check": "prettier --check ."
+ "check": "prettier --check .",
+ "lint": "eslint --ext .ts,.tsx .",
+ "lint:fix": "npm run lint -- --fix"
},
"dependencies": {
"@firebase/auth": "^1.5.1",
- "@react-native-async-storage/async-storage": "^1.21.0",
- "@fortawesome/fontawesome-svg-core": "^6.5.1",
- "@fortawesome/free-brands-svg-icons": "^6.5.1",
- "@fortawesome/free-regular-svg-icons": "^6.5.1",
- "@fortawesome/free-solid-svg-icons": "^6.5.1",
- "@fortawesome/react-native-fontawesome": "^0.3.0",
+ "@react-native-async-storage/async-storage": "1.18.2",
"@react-navigation/bottom-tabs": "^6.5.11",
"@react-navigation/native": "^6.1.9",
"@react-navigation/native-stack": "^6.9.17",
+ "@tanstack/react-query": "^5.18.1",
"axios": "^1.6.4",
+ "clsx": "^2.1.0",
"expo": "~49.0.13",
"expo-document-picker": "~11.5.4",
"expo-file-system": "~15.4.5",
@@ -33,13 +32,24 @@
"react-native": "0.72.6",
"react-native-paper": "^5.12.3",
"react-native-safe-area-context": "4.6.3",
- "react-native-svg-transformer": "^1.3.0",
- "react-native-screens": "~3.22.0"
+ "react-native-screens": "~3.22.0",
+ "react-native-svg-transformer": "^1.3.0"
},
"devDependencies": {
"@babel/core": "^7.20.0",
+ "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/react": "~18.2.14",
- "prettier": "^3.1.1",
+ "@typescript-eslint/eslint-plugin": "^6.21.0",
+ "@typescript-eslint/parser": "^6.21.0",
+ "eslint": "^8.56.0",
+ "eslint-config-prettier": "^9.1.0",
+ "eslint-import-resolver-typescript": "^3.6.1",
+ "eslint-plugin-filenames": "^1.3.2",
+ "eslint-plugin-import": "^2.29.1",
+ "eslint-plugin-prettier": "^5.1.3",
+ "eslint-plugin-react": "^7.33.2",
+ "prettier": "^3.2.5",
+ "prettier-plugin-tailwindcss": "^0.5.11",
"tailwindcss": "3.3.2",
"typescript": "^5.1.3"
},
diff --git a/client/screens/Landing.tsx b/client/screens/Landing.tsx
deleted file mode 100644
index e69de29..0000000
diff --git a/client/screens/Login.tsx b/client/screens/Login.tsx
deleted file mode 100644
index 71d8cf5..0000000
--- a/client/screens/Login.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import React, { useState } from 'react';
-import { View, TextInput, Button, Alert } from 'react-native';
-import { logIn } from '../services/auth/login';
-import { signUp } from '../services/auth/signup';
-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 handleLogin = async () => {
- if (!email || !password) {
- Alert.alert('Error', 'Email and password are required');
- return;
- }
- const result = await logIn(email, password);
- if (typeof result === 'string') {
- Alert.alert('Login Failed', result.substring(5).replaceAll('-', ' '));
- } else {
- Alert.alert('Login Success', 'Welcome back!');
- // console.log('result: ', result);
- navigation.navigate('MainNavScreens');
- }
- };
-
- const handleSignUp = async () => {
- if (!email || !password) {
- Alert.alert('Error', 'Email and password are required');
- return;
- }
- const result = await signUp(email, password);
- if (typeof result === 'string') {
- Alert.alert('Signup Failed', result.substring(5).replaceAll('-', ' '));
- } else {
- Alert.alert('Signup Success', 'Welcome to the app!');
- // console.log('result: ', result);
- navigation.navigate('MainNavScreens');
- }
- };
-
- return (
-
-
-
-
-
-
- );
-};
-
-export default LoginPage;
diff --git a/client/screens/LoginPage.tsx b/client/screens/LoginPage.tsx
new file mode 100644
index 0000000..21051a9
--- /dev/null
+++ b/client/screens/LoginPage.tsx
@@ -0,0 +1,77 @@
+import React, { useState } from 'react';
+import { Alert, Pressable, Text, TextInput, View } from 'react-native';
+
+import { onAuthStateChanged } from '@firebase/auth';
+import { useNavigation } from '@react-navigation/native';
+
+import { auth } from '../firebase.config';
+import { AppStackNavigation } from '../navigation/AppNavigation';
+import { useAuth } from '../services/auth';
+
+export default function LoginPage() {
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const { logInMutation, signUpMutation } = useAuth();
+
+ const navigation = useNavigation();
+
+ onAuthStateChanged(auth, (user) => {
+ if (user) {
+ navigation.navigate('Main');
+ return;
+ }
+
+ navigation.navigate('Login');
+ });
+
+ const handleLogin = async () => {
+ if (!email || !password) {
+ Alert.alert('Error', 'Email and password are required');
+ return;
+ }
+
+ logInMutation({ email, password });
+ };
+
+ const handleSignUp = async () => {
+ if (!email || !password) {
+ Alert.alert('Error', 'Email and password are required');
+ return;
+ }
+
+ signUpMutation({ email, password });
+ };
+
+ return (
+
+
+
+
+ Log In
+
+
+
+ Sign Up
+
+
+
+ );
+}
diff --git a/client/screens/Medication.tsx b/client/screens/Medication.tsx
deleted file mode 100644
index 0ddfbc0..0000000
--- a/client/screens/Medication.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import * as React from 'react';
-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 (
-
-
-
- {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/screens/MedicationList.tsx b/client/screens/MedicationList.tsx
new file mode 100644
index 0000000..634fa39
--- /dev/null
+++ b/client/screens/MedicationList.tsx
@@ -0,0 +1,162 @@
+import React, { useState } from 'react';
+import {
+ ActivityIndicator,
+ Pressable,
+ ScrollView,
+ Text,
+ TextInput,
+ View
+} from 'react-native';
+
+import { clsx } from 'clsx';
+import { Divider } from 'react-native-paper';
+
+import { ClickableCard } from '../components/ClickableCard';
+import { DocPickerButton } from '../components/DocPickerButton';
+import { PopupModal } from '../components/PopupModal';
+import { useCareWalletContext } from '../contexts/CareWalletContext';
+import { useAuth } from '../services/auth';
+import { useMedication } from '../services/medication';
+import { Medication } from '../types/medication';
+
+export default function MedicationList() {
+ const [selectedMed, setSelectedMed] = useState();
+
+ const [newMedState, setNewMedState] = useState({ id: '', name: '' });
+
+ const [medVisible, setMedVisible] = useState(false);
+ const [newMedVisible, setNewMedVisible] = useState(false);
+ const [userGroupVisible, setUserGroupVisible] = useState(false);
+
+ const { user, group } = useCareWalletContext();
+
+ const { medications, medicationsIsLoading, addMedicationMutation } =
+ useMedication();
+
+ const { signOutMutation } = useAuth();
+
+ if (medicationsIsLoading)
+ return (
+
+
+ Loading Medications...
+
+ );
+
+ if (!medications)
+ return (
+
+ Could Not Load Medications List
+
+ );
+
+ return (
+
+
+
+ {selectedMed?.medication_name}
+
+ ID: {selectedMed?.medication_id}
+
+
+
+ ID:
+ setNewMedState({ ...newMedState, id: val })}
+ value={newMedState.id}
+ />
+
+
+ Name:
+
+ setNewMedState({ ...newMedState, name: val })
+ }
+ value={newMedState.name}
+ />
+
+ {
+ addMedicationMutation({
+ medication_id: parseInt(newMedState.id),
+ medication_name: newMedState.name
+ });
+ setNewMedVisible(false);
+ }}
+ >
+
+ Add New Medication
+
+
+
+
+
+ setNewMedVisible(true)}
+ className="mb-2 ml-2 mt-2 self-center rounded-md border border-carewallet-gray pl-1 pr-1"
+ >
+
+ Add New Medication
+
+
+
+
+ {medications.map((med, index) => (
+
+ {
+ setSelectedMed(med);
+ setMedVisible(true);
+ }}
+ >
+
+ ID: {med.medication_id}
+
+
+ {index !== medications.length - 1 ? : null}
+
+ ))}
+
+ setUserGroupVisible(true)}
+ className="mb-2 w-80 self-center rounded-md border border-carewallet-gray "
+ >
+
+ Show User and Group Info
+
+
+
+
+ User ID: {user.userID}
+
+ User Email: {user.userEmail}
+
+ Group ID: {group.groupID}
+ Group Role: {group.role}
+
+ {
+ setUserGroupVisible(false);
+ signOutMutation();
+ }}
+ className="w-20 self-center rounded-md border border-carewallet-gray"
+ >
+
+ Sign Out
+
+
+
+
+ );
+}
diff --git a/client/services/auth.ts b/client/services/auth.ts
new file mode 100644
index 0000000..8863b6d
--- /dev/null
+++ b/client/services/auth.ts
@@ -0,0 +1,80 @@
+import { Alert } from 'react-native';
+
+import { useMutation, useQueryClient } from '@tanstack/react-query';
+import {
+ createUserWithEmailAndPassword,
+ onAuthStateChanged as firebaseOnAuthStateChanged,
+ signInWithEmailAndPassword,
+ User,
+ UserCredential
+} from 'firebase/auth';
+
+import { auth } from '../firebase.config';
+
+interface AuthProps {
+ email: string;
+ password: string;
+}
+
+const logIn = async ({ email, password }: AuthProps): Promise =>
+ await signInWithEmailAndPassword(auth, email, password);
+
+const signUp = async ({
+ email,
+ password
+}: AuthProps): Promise =>
+ await createUserWithEmailAndPassword(auth, email, password);
+
+const signOut = async () => await auth.signOut();
+
+// TODO: update to use a toast instead of an alert
+export const useAuth = () => {
+ const queryClient = useQueryClient();
+
+ const { mutate: logInMutation } = useMutation({
+ mutationFn: (authProps: AuthProps) => logIn(authProps),
+ onSuccess: () => {
+ Alert.alert('Login Success', 'Welcome back!');
+ },
+ onError: (error) => {
+ Alert.alert('Login Failed', error.message);
+ }
+ });
+
+ const { mutate: signUpMutation } = useMutation({
+ mutationFn: (authProps: AuthProps) => signUp(authProps),
+ onSuccess: () => {
+ Alert.alert('Signup Success', 'Welcome to the app!');
+ },
+ onError: (error) => {
+ Alert.alert('Error Signing Up: ', error.message);
+ }
+ });
+
+ const { mutate: signOutMutation } = useMutation({
+ mutationFn: () => signOut(),
+ onSuccess: () => {
+ Alert.alert('Sign Out Success', 'You have been signed out');
+ queryClient.invalidateQueries({
+ queryKey: ['medList'] // mark medlist as stale so it refetches on signin
+ });
+ },
+ onError: (error) => {
+ Alert.alert('Error Signing Out: ', error.message);
+ }
+ });
+
+ return {
+ logInMutation,
+ signUpMutation,
+ signOutMutation
+ };
+};
+
+export const onAuthStateChanged = (
+ callback: (user: User | null) => void
+): void => {
+ firebaseOnAuthStateChanged(auth, (user: User | null) => {
+ callback(user);
+ });
+};
diff --git a/client/services/auth/authState.ts b/client/services/auth/authState.ts
deleted file mode 100644
index d454711..0000000
--- a/client/services/auth/authState.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { auth } from '../../firebase.config';
-import {
- onAuthStateChanged as firebaseOnAuthStateChanged,
- User
-} from 'firebase/auth';
-
-export const onAuthStateChanged = (
- callback: (user: User | null) => void
-): void => {
- firebaseOnAuthStateChanged(auth, (user: User | null) => {
- callback(user);
- });
-};
diff --git a/client/services/auth/login.ts b/client/services/auth/login.ts
deleted file mode 100644
index 9173a18..0000000
--- a/client/services/auth/login.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { auth } from '../../firebase.config';
-import {
- createUserWithEmailAndPassword,
- signInWithEmailAndPassword,
- User
-} from 'firebase/auth';
-
-export const logIn = async (
- email: string,
- password: string
-): Promise => {
- try {
- const userCredential = await signInWithEmailAndPassword(
- auth,
- email,
- password
- );
- return userCredential.user;
- } catch (error: any) {
- console.error('Error logging in: ', error.code);
- if (error.code === 'auth/user-not-found') {
- try {
- const newUserCredential = await createUserWithEmailAndPassword(
- auth,
- email,
- password
- );
- return newUserCredential.user;
- } catch (creationError: any) {
- console.error('Error creating user: ', creationError.code);
- return creationError.code;
- }
- } else {
- return error.code;
- }
- }
-};
diff --git a/client/services/auth/signup.ts b/client/services/auth/signup.ts
deleted file mode 100644
index 4e13d70..0000000
--- a/client/services/auth/signup.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { auth } from '../../firebase.config';
-import { User } from 'firebase/auth';
-import { createUserWithEmailAndPassword } from 'firebase/auth';
-
-export const signUp = async (
- email: string,
- password: string
-): Promise => {
- try {
- const userCredential = await createUserWithEmailAndPassword(
- auth,
- email,
- password
- );
- return userCredential.user;
- } catch (error: any) {
- console.error('Error signing up: ', error.code);
- return error.code;
- }
-};
diff --git a/client/services/auth/tests/login.test.ts b/client/services/auth/tests/login.test.ts
deleted file mode 100644
index 4f92c30..0000000
--- a/client/services/auth/tests/login.test.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-// import { mockAuth } from 'firebase-mock';
-// import { logIn } from 'client/services/auth/login.ts'; // Adjust path to match your project structure
-
-// describe('logIn function', () => {
-// const auth = mockAuth();
-
-// it('should log in with valid credentials', async () => {
-// auth.signInWithEmailAndPassword.mockResolvedValueOnce({
-// user: {
-// uid: 'mock-user-id',
-// email: 'user@example.com',
-// // ...other user properties
-// },
-// });
-
-// const user = await logIn('user@example.com', 'password');
-
-// expect(user).toBeDefined();
-// expect(user.uid).toBe('mock-user-id');
-// expect(user.email).toBe('user@example.com');
-// });
-
-// it('should handle invalid credentials', async () => {
-// auth.signInWithEmailAndPassword.mockRejectedValueOnce(new Error('Invalid email or password'));
-
-// const user = await logIn('invalid@email.com', 'wrongpassword');
-
-// expect(user).toBeNull();
-// });
-// });
diff --git a/client/services/file.ts b/client/services/file.ts
index 05cba91..6a25312 100644
--- a/client/services/file.ts
+++ b/client/services/file.ts
@@ -1,33 +1,61 @@
-import axios, { HttpStatusCode } from 'axios';
+import { useMutation } from '@tanstack/react-query';
+import { HttpStatusCode } from 'axios';
+import { DocumentPickerAsset } from 'expo-document-picker';
+import {
+ createUploadTask,
+ FileSystemUploadResult,
+ FileSystemUploadType
+} from 'expo-file-system';
+
import { api_url } from './api-links';
-import * as DocumentPicker from 'expo-document-picker';
-import * as FileSystem from 'expo-file-system';
-export const uploadFile = async (
- file: DocumentPicker.DocumentPickerAsset,
- userId: number,
- groupId: number
-) => {
- const uploadResumable = FileSystem.createUploadTask(
+interface UploadFileProps {
+ file: DocumentPickerAsset;
+ userId: string;
+ groupId: number;
+}
+
+const uploadFile = async ({
+ file,
+ userId,
+ groupId
+}: UploadFileProps): Promise => {
+ const uploadResumable = createUploadTask(
`${api_url}/files/upload`,
file.uri,
{
httpMethod: 'POST',
- uploadType: FileSystem.FileSystemUploadType.MULTIPART,
+ uploadType: FileSystemUploadType.MULTIPART,
fieldName: 'file_data',
- headers: {
- user_id: userId.toString(),
+ parameters: {
+ upload_by: userId,
group_id: groupId.toString()
}
}
);
- await uploadResumable.uploadAsync().then((res) => {
- if (res && res.status === HttpStatusCode.Ok) {
- console.log('File uploaded!');
- return res.status;
+ return await uploadResumable.uploadAsync();
+};
+
+export const useFile = () => {
+ const { mutate: uploadFileMutation } = useMutation({
+ mutationFn: (fileUploadProps: UploadFileProps) =>
+ uploadFile(fileUploadProps),
+ onSuccess: (result) => {
+ // This is needed for file upload since it seems to use fetch instead of axios
+ // axios results in error if the status is 400+
+ if (result && result.status === HttpStatusCode.Ok) {
+ console.log('File Uploaded...');
+ return;
+ }
+ console.log('Failed to Upload File...');
+ },
+ onError: (error) => {
+ console.log('Server Error: ', error.message);
}
- console.log('Upload failed!');
- throw new Error(res?.body);
});
+
+ return {
+ uploadFileMutation
+ };
};
diff --git a/client/services/medication.ts b/client/services/medication.ts
index d70b0ab..1b7669a 100644
--- a/client/services/medication.ts
+++ b/client/services/medication.ts
@@ -1,8 +1,73 @@
+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import axios from 'axios';
-import { api_url } from './api-links';
+
import { Medication } from '../types/medication';
+import { api_url } from './api-links';
+
+const getAllMedications = async (): Promise => {
+ await new Promise((r) => setTimeout(r, 2000)); // this is just to show is loading (DONT ADD ANYWHERE ELSE)
+ const { data } = await axios.get(`${api_url}/medications`);
+ return data;
+};
+
+const addMedication = async (med: Medication): Promise => {
+ const { status } = await axios.post(`${api_url}/medications`, {
+ medication_id: med.medication_id,
+ medication_name: med.medication_name
+ });
+
+ return status;
+};
+
+export const useMedication = () => {
+ const queryClient = useQueryClient();
+
+ const { data: medications, isLoading: medicationsIsLoading } = useQuery<
+ Medication[]
+ >({
+ queryKey: ['medList'], // if querying with a value add values here ex. ['medList', {id}]
+ queryFn: getAllMedications,
+ refetchInterval: 20000 // Will refetch the data every 20 seconds
+ });
+
+ const { mutate: addMedicationMutation } = useMutation({
+ mutationFn: (med: Medication) => addMedication(med),
+ //Optimistically update the medlist so it looks like the mutation has been completed
+ onMutate: async (newMed) => {
+ // Cancel any outgoing refetches
+ // (so they don't overwrite the optimistic update)
+ await queryClient.cancelQueries({
+ queryKey: ['medList', newMed.medication_id]
+ });
+
+ // Snapshot the previous value
+ const previousMedication = queryClient.getQueryData(['medList']);
+
+ // Optimistically update to the new value
+ queryClient.setQueryData(['medList'], (old: Medication[]) => [
+ ...old,
+ newMed
+ ]);
+
+ // Return a context with the previous and new todo
+ return { previousMedication };
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: ['medList'] // mark medlist as stale so it refetches
+ });
+ return;
+ },
+ // If the mutation fails, use the context we returned above to return to the previous state
+ onError: (err, newMed, context) => {
+ console.log('ERROR: Failed to Add Medication...');
+ queryClient.setQueryData(['medList'], context?.previousMedication);
+ }
+ });
-export const getAllMedications = async (): Promise => {
- const response = await axios.get(`${api_url}/medications`);
- return response.data;
+ return {
+ medications,
+ medicationsIsLoading,
+ addMedicationMutation
+ };
};
diff --git a/client/tailwind.config.js b/client/tailwind.config.js
index 2a6c6f1..e4c198a 100644
--- a/client/tailwind.config.js
+++ b/client/tailwind.config.js
@@ -1,13 +1,19 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
- './App.{js,jsx,ts,tsx}',
- './screens/**/*.{js,jsx,ts,tsx}',
- './components/**/*.{js,jsx,ts,tsx}',
- './navigation/**/*.{js,jsx,ts,tsx}'
+ './**/*.html',
+ './**/*.js',
+ './**/*.jsx',
+ './**/*.ts',
+ './**/*.tsx'
],
theme: {
- extend: {}
+ colors: {
+ 'carewallet-white': '#FFFFFF',
+ 'carewallet-black': '#000000',
+ 'carewallet-gray': '#BEBEBE',
+ 'carewallet-lightgray': '#D9D9D9'
+ }
},
plugins: []
};
diff --git a/cz.yaml b/cz.yaml
deleted file mode 100644
index b823e32..0000000
--- a/cz.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-commitizen:
- major_version_zero: true
- name: cz_conventional_commits
- tag_format: $version
- update_changelog_on_bump: true
- version: 0.0.1
- version_scheme: semver