From 652626f51f8e2a18db7b2191232c3bce894a6584 Mon Sep 17 00:00:00 2001 From: tahmid-saj Date: Sun, 19 May 2024 21:15:27 -0400 Subject: [PATCH] changing dir structure --- .gitignore | 6 - .../api-requests/calories-burned.requests.js | 136 ++++++ src/utils/api-requests/chatbot.requests.js | 23 + .../nutrient-predictor.requests.js | 53 +++ .../nutrition-tracker.requests.js | 133 ++++++ src/utils/api-requests/recipes.requests.js | 53 +++ .../calories-burned.calculations.js | 40 ++ .../nutrition-tracker.calculations.js | 28 ++ .../calculations/recipes.calculations.js | 21 + .../constants/calories-burned.constants.js | 12 + src/utils/constants/chatbot.constants.js | 1 + .../constants/nutrient-predictor.constants.js | 12 + .../constants/nutrition-tracker.constants.js | 20 + src/utils/constants/recipes.constants.js | 10 + src/utils/constants/shared.constants.js | 169 +++++++ src/utils/errors/calories-burned.errors.js | 27 ++ src/utils/errors/chatbot.errors.js | 9 + src/utils/errors/nutrient-predictor.errors.js | 11 + src/utils/errors/nutrition-tracker.errors.js | 56 +++ src/utils/errors/recipes.errors.js | 9 + src/utils/errors/user.errors.js | 17 + .../nutrition-predictor.external.js | 419 ++++++++++++++++++ src/utils/firebase/firebase.utils.js | 132 ++++++ .../calories-burned.validations.js | 46 ++ src/utils/validations/chatbot.validation.js | 12 + .../nutrient-predictor.validations.js | 32 ++ .../nutrition-tracker.validations.js | 156 +++++++ src/utils/validations/recipes.validations.js | 14 + 28 files changed, 1651 insertions(+), 6 deletions(-) create mode 100644 src/utils/api-requests/calories-burned.requests.js create mode 100644 src/utils/api-requests/chatbot.requests.js create mode 100644 src/utils/api-requests/nutrient-predictor.requests.js create mode 100644 src/utils/api-requests/nutrition-tracker.requests.js create mode 100644 src/utils/api-requests/recipes.requests.js create mode 100644 src/utils/calculations/calories-burned.calculations.js create mode 100644 src/utils/calculations/nutrition-tracker.calculations.js create mode 100644 src/utils/calculations/recipes.calculations.js create mode 100644 src/utils/constants/calories-burned.constants.js create mode 100644 src/utils/constants/chatbot.constants.js create mode 100644 src/utils/constants/nutrient-predictor.constants.js create mode 100644 src/utils/constants/nutrition-tracker.constants.js create mode 100644 src/utils/constants/recipes.constants.js create mode 100644 src/utils/constants/shared.constants.js create mode 100644 src/utils/errors/calories-burned.errors.js create mode 100644 src/utils/errors/chatbot.errors.js create mode 100644 src/utils/errors/nutrient-predictor.errors.js create mode 100644 src/utils/errors/nutrition-tracker.errors.js create mode 100644 src/utils/errors/recipes.errors.js create mode 100644 src/utils/errors/user.errors.js create mode 100644 src/utils/external-js/nutrition-predictor.external.js create mode 100644 src/utils/firebase/firebase.utils.js create mode 100644 src/utils/validations/calories-burned.validations.js create mode 100644 src/utils/validations/chatbot.validation.js create mode 100644 src/utils/validations/nutrient-predictor.validations.js create mode 100644 src/utils/validations/nutrition-tracker.validations.js create mode 100644 src/utils/validations/recipes.validations.js diff --git a/.gitignore b/.gitignore index 5472ff1..dc253ca 100644 --- a/.gitignore +++ b/.gitignore @@ -8,12 +8,6 @@ # env variables .env -# firebase -/src/utils -/src/utils -/src/utils/firebase -/src/utils/firebase/firebase.utils.jsx - # testing /coverage diff --git a/src/utils/api-requests/calories-burned.requests.js b/src/utils/api-requests/calories-burned.requests.js new file mode 100644 index 0000000..055b871 --- /dev/null +++ b/src/utils/api-requests/calories-burned.requests.js @@ -0,0 +1,136 @@ +import { errorOnGetSearchActivity, + errorOnGetTrackedCaloriesBurned, + errorOnPostAddActivity, errorOnDeleteRemoveActivity, + errorOnPutTrackedCaloriesBurned + } from "../errors/calories-burned.errors" + +// calories burned api requests + +// helper functions +async function processSearchedActivity(trackedDayInfo, activityResults) { + console.log(trackedDayInfo) + + return activityResults.map((activityResult) => { + return { + activity: String(activityResult.name), + searchedActivity: String(trackedDayInfo.activity), + dateTracked: String(trackedDayInfo.dateTracked), + caloriesBurnedPerHour: Number(activityResult.calories_per_hour), + durationMinutes: Number(activityResult.duration_minutes), + totalCaloriesBurned: Number(activityResult.total_calories) + } + }) +} + +// searching activity +export async function getSearchActivity(trackedDayInfo) { + try { + let url = `${process.env.REACT_APP_API_NINJAS_CALORIES_BURNED_URL}${trackedDayInfo.activity}` + + if (trackedDayInfo.weightPounds !== "") { + url = url + `&weight=${trackedDayInfo.weightPounds}` + } + if (trackedDayInfo.durationMinutes !== "") { + url = url + `&duration=${trackedDayInfo.durationMinutes}` + } + + const resActivityResults = await fetch(`${url}`, { + method: "GET", + headers: { + "X-Api-Key": `${process.env.REACT_APP_API_NINJAS_KEY}` + } + }) + + const resJSON = await resActivityResults.json() + const res = await processSearchedActivity(trackedDayInfo, resJSON) + return res + } catch (error) { + errorOnGetSearchActivity() + + if (error) { + return console.error("Request failed: ", error) + } + } +} + +// signing in +export async function getTrackedCaloriesBurned(userId, email) { + try { + console.log(`${process.env.REACT_APP_API_URL_TRACKED_CALORIES_BURNED}/${userId}/${email}`) + const response = await fetch(`${process.env.REACT_APP_API_URL_TRACKED_CALORIES_BURNED}/${userId}/${email}`) + + return response.json() + } catch (error) { + console.log(error) + errorOnGetTrackedCaloriesBurned() + } +} + +// calories burned operations +export async function postAddActivity(userId, email, trackedDayInfo, activityId) { + try { + console.log(`${process.env.REACT_APP_API_URL_TRACKED_CALORIES_BURNED}/${userId}/${email}/${process.env.REACT_APP_API_URL_POST_TRACKED_CALORIES_BURNED}`) + const response = await fetch(`${process.env.REACT_APP_API_URL_TRACKED_CALORIES_BURNED}/${userId}/${email}/${process.env.REACT_APP_API_URL_POST_TRACKED_CALORIES_BURNED}`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + dateTracked: trackedDayInfo.dateTracked, + activity: trackedDayInfo.activity, + durationMinutes: trackedDayInfo.durationMinutes, + caloriesBurnedPerHour: trackedDayInfo.caloriesBurnedPerHour, + totalCaloriesBurned: trackedDayInfo.totalCaloriesBurned, + activityId: activityId + }) + }) + + console.log("resp") + + return response.status + } catch (error) { + console.log(error) + errorOnPostAddActivity() + } +} + +export async function deleteRemoveActivity(userId, email, activityId) { + try { + console.log(`${process.env.REACT_APP_API_URL_TRACKED_CALORIES_BURNED}/${userId}/${email}/${process.env.REACT_APP_API_URL_DELETE_TRACKED_CALORIES_BURNED}`) + const response = await fetch(`${process.env.REACT_APP_API_URL_TRACKED_CALORIES_BURNED}/${userId}/${email}/${process.env.REACT_APP_API_URL_DELETE_TRACKED_CALORIES_BURNED}`, { + method: "DELETE", + headers: { + "Content-Type": "text/plain" + }, + body: String(activityId) + }) + + console.log("removed") + + return response.status + } catch (error) { + console.log(error) + errorOnDeleteRemoveActivity() + } +} + +// signing out +export async function putTrackedCaloriesBurned(userId, email, trackedCaloriesBurned) { + try { + console.log(`${process.env.REACT_APP_API_URL_TRACKED_CALORIES_BURNED}/${userId}/${email}/${process.env.REACT_APP_API_URL_PUT_TRACKED_CALORIES_BURNED}`) + const response = await fetch(`${process.env.REACT_APP_API_URL_TRACKED_CALORIES_BURNED}/${userId}/${email}/${process.env.REACT_APP_API_URL_PUT_TRACKED_CALORIES_BURNED}`, { + method: "PUT", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + trackedCaloriesBurned: trackedCaloriesBurned + }) + }) + + return response.status + } catch (error) { + console.log(error) + errorOnPutTrackedCaloriesBurned() + } +} \ No newline at end of file diff --git a/src/utils/api-requests/chatbot.requests.js b/src/utils/api-requests/chatbot.requests.js new file mode 100644 index 0000000..1c12448 --- /dev/null +++ b/src/utils/api-requests/chatbot.requests.js @@ -0,0 +1,23 @@ +import { errorOnGetChatBotResponse } from "../errors/chatbot.errors"; + +// chatbot api requests + +// response +export const getChatBotResponse = async (messageInput) => { + try { + const response = await fetch(`${process.env.REACT_APP_API_URL_CHATBOT}`, { + method: "POST", + headers: { + "Content-Type": "text/plain" + }, + body: String(messageInput) + }) + + const { message } = await response.json() + return message + } catch (error) { + console.log("Error getting chatbot response") + errorOnGetChatBotResponse() + } +} + diff --git a/src/utils/api-requests/nutrient-predictor.requests.js b/src/utils/api-requests/nutrient-predictor.requests.js new file mode 100644 index 0000000..0aef7c5 --- /dev/null +++ b/src/utils/api-requests/nutrient-predictor.requests.js @@ -0,0 +1,53 @@ +import { errorOnGetNutrientPredictions } from "../errors/nutrient-predictor.errors"; + +// helper functions +const processNutrientPredictions = async (nutrientPredictions) => { + return nutrientPredictions.map((nutrientPrediction) => { + return { + name: nutrientPrediction.name, + servingSizeG: nutrientPrediction.serving_size_g, + calories: nutrientPrediction.calories, + macronutrients: { + carbohydratesTotalG: nutrientPrediction.carbohydrates_total_g, + proteinG: nutrientPrediction.protein_g, + fatTotalG: nutrientPrediction.fat_total_g, + fatSaturatedG: nutrientPrediction.fat_saturated_g, + }, + micronutrients: { + sodiumMG: nutrientPrediction.sodium_mg, + potassiumMG: nutrientPrediction.potassium_mg, + cholesterolMg: nutrientPrediction.cholesterol_mg, + fiberG: nutrientPrediction.fiber_g, + sugarG: nutrientPrediction.sugar_g + } + } + }) +} + +// nutrient predictor api requests + +export const getMealPredictions = async (image) => { + return "1 pound of steak with mashed potatoes and a can of sprite" +}; + +export const getNutrientPredictions = async (mealDescription) => { + try { + const resNutrientPredictions = await fetch(`${process.env.REACT_APP_API_NINJAS_NUTRIENT_PREDICTOR_URL}${mealDescription}`, { + method: "GET", + headers: { + "X-Api-Key": `${process.env.REACT_APP_API_NINJAS_KEY}` + } + }) + + const resJSON = await resNutrientPredictions.json() + const res = await processNutrientPredictions(resJSON) + console.log(res) + return res + } catch (error) { + errorOnGetNutrientPredictions() + + if (error) { + return console.error("Request failed: ", error) + } + } +} \ No newline at end of file diff --git a/src/utils/api-requests/nutrition-tracker.requests.js b/src/utils/api-requests/nutrition-tracker.requests.js new file mode 100644 index 0000000..e57832b --- /dev/null +++ b/src/utils/api-requests/nutrition-tracker.requests.js @@ -0,0 +1,133 @@ +import { errorOnGetNutritionTrackedDaysData, errorOnGetNutritionTrackedDaysSummaryData, + errorOnPostNutritionTrackedDay, errorOnDeleteNutritionTrackedDay, errorOnPutNutritionTrackedDay, + errorOnPutNutritionTrackedDays, errorOnPutNutritionTrackedDaysSummary } from "../errors/nutrition-tracker.errors"; + +// nutrition tracker api requests + +// sign in +export const getNutritionTrackedDaysData = async (userId, email) => { + try { + console.log(`${process.env.REACT_APP_API_URL_NUTRITION_TRACKED_DAYS}/${userId}/${email}`); + const response = await fetch(`${process.env.REACT_APP_API_URL_NUTRITION_TRACKED_DAYS}/${userId}/${email}`); + + return response.json(); + } catch (error) { + console.log(error); + errorOnGetNutritionTrackedDaysData(); + } +}; + +export const getNutritionTrackedDaysSummaryData = async (userId, email) => { + try { + console.log(`${process.env.REACT_APP_API_URL_NUTRITION_TRACKED_DAYS_SUMMARY}/${userId}/${email}`); + const response = await fetch(`${process.env.REACT_APP_API_URL_NUTRITION_TRACKED_DAYS_SUMMARY}/${userId}/${email}`); + + return response.json(); + } catch (error) { + console.log(error); + errorOnGetNutritionTrackedDaysSummaryData(); + } +}; + +// nutrition tracked days operations +export const postNutritionTrackedDay = async (userId, email, nutritionTrackedDay) => { + try { + console.log(`${process.env.REACT_APP_API_URL_NUTRITION_TRACKED_DAYS}/${userId}/${email}/${process.env.REACT_APP_API_URL_POST_NUTRITION_TRACKED_DAY}`); + const response = await fetch(`${process.env.REACT_APP_API_URL_NUTRITION_TRACKED_DAYS}/${userId}/${email}/${process.env.REACT_APP_API_URL_POST_NUTRITION_TRACKED_DAY}`, { + method: 'POST', + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(nutritionTrackedDay) + }); + + return response.status; + } catch (error) { + console.log(error); + errorOnPostNutritionTrackedDay(); + } +}; + +export const deleteNutritionTrackedDay = async (userId, email, trackedDay) => { + try { + console.log(`${process.env.REACT_APP_API_URL_NUTRITION_TRACKED_DAYS}/${userId}/${email}/${process.env.REACT_APP_API_URL_DELETE_NUTRITION_TRACKED_DAY}`) + const response = await fetch(`${process.env.REACT_APP_API_URL_NUTRITION_TRACKED_DAYS}/${userId}/${email}/${process.env.REACT_APP_API_URL_DELETE_NUTRITION_TRACKED_DAY}`, { + method: "DELETE", + headers: { + "Content-Type": "text/plain" + }, + body: String(trackedDay) + }) + + console.log("removed") + + return response.status + } catch (error) { + console.log(error) + errorOnDeleteNutritionTrackedDay() + } +} + +export const putNutritionTrackedDay = async (userId, email, originalNutritionTrackedDay, updatedNutritionTrackedDay) => { + try { + const nutritionTrackedDayInfo = { + originalNutritionTrackedDay: originalNutritionTrackedDay, + updatedNutritionTrackedDay: updatedNutritionTrackedDay, + } + + console.log(`${process.env.REACT_APP_API_URL_NUTRITION_TRACKED_DAYS}/${userId}/${email}/${process.env.REACT_APP_API_URL_PUT_NUTRITION_TRACKED_DAY}`); + const response = await fetch(`${process.env.REACT_APP_API_URL_NUTRITION_TRACKED_DAYS}/${userId}/${email}/${process.env.REACT_APP_API_URL_PUT_NUTRITION_TRACKED_DAY}`, { + method: 'PUT', + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(nutritionTrackedDayInfo) + }); + + return response.status; + } catch (error) { + console.log(error); + errorOnPutNutritionTrackedDay(); + } +}; + +// sign out +export const putNutritionTrackedDays = async (userId, email, nutritionTrackedDays) => { + try { + console.log(`${process.env.REACT_APP_API_URL_NUTRITION_TRACKED_DAYS}/${userId}/${email}`); + const response = await fetch(`${process.env.REACT_APP_API_URL_NUTRITION_TRACKED_DAYS}/${userId}/${email}`, { + method: "PUT", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + nutritionTrackedDays: nutritionTrackedDays + }) + }); + + return response.status; + } catch (error) { + console.log(error); + errorOnPutNutritionTrackedDays(); + } +}; + +export const putNutritionTrackedDaysSummary = async (userId, email, nutritionTrackedDaysSummary) => { + try { + console.log(`${process.env.REACT_APP_API_URL_NUTRITION_TRACKED_DAYS_SUMMARY}/${userId}/${email}`); + const response = await fetch(`${process.env.REACT_APP_API_URL_NUTRITION_TRACKED_DAYS_SUMMARY}/${userId}/${email}`, { + method: "PUT", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + nutritionTrackedDaysSummary: nutritionTrackedDaysSummary + }) + }); + + return response.status; + } catch (error) { + console.log(error); + errorOnPutNutritionTrackedDaysSummary(); + } +}; diff --git a/src/utils/api-requests/recipes.requests.js b/src/utils/api-requests/recipes.requests.js new file mode 100644 index 0000000..a908bef --- /dev/null +++ b/src/utils/api-requests/recipes.requests.js @@ -0,0 +1,53 @@ +import { errorOnDisplaySearchedRecipes } from "../errors/recipes.errors"; + +import { TIMEOUT_SEC } from "../constants/recipes.constants"; + +// API requests for recipes + +// helpers functions + +const timeout = (seconds) => { + return new Promise(function (_, reject) { + setTimeout(function () { + reject(new Error(`Request took too long! Timeout after ${seconds} seconds`)); + }, seconds * 1000); + }); +}; + +// requests + +export const getRecipes = async (recipe) => { + try { + console.log(`${process.env.REACT_APP_RECIPES_URL}${recipe}?${process.env.REACT_APP_RECIPES_API_KEY_NAME}${process.env.REACT_APP_RECIPES_API_KEY}`); + const fetchPromiseRecipes = fetch(`${process.env.REACT_APP_RECIPES_URL}${recipe}?${process.env.REACT_APP_RECIPES_API_KEY_NAME}${process.env.REACT_APP_RECIPES_API_KEY}`) + const resRecipes = await Promise.race([fetchPromiseRecipes, timeout(TIMEOUT_SEC)]); + const dataRecipes = await resRecipes.json(); + + if (!resRecipes.ok) { + throw new Error(`${dataRecipes.message} (${dataRecipes.status})`); + } + + return dataRecipes.data.recipes; + } catch (error) { + errorOnDisplaySearchedRecipes(recipe); + console.log(error); + } +}; + +export const getRecipe = async (recipe) => { + try { + const fetchPromiseRecipe = fetch(`${process.env.REACT_APP_RECIPE_URL}${recipe.id}?${process.env.REACT_APP_RECIPES_API_KEY_NAME}${process.env.REACT_APP_RECIPES_API_KEY}`); + const resRecipe = await Promise.race([fetchPromiseRecipe, timeout(TIMEOUT_SEC)]); + const dataRecipe = await resRecipe.json(); + + if (!resRecipe.ok) { + throw new Error(`${dataRecipe.message} (${dataRecipe.status})`); + } + + return dataRecipe.data.recipe; + } catch (error) { + errorOnDisplaySearchedRecipes(recipe.title); + console.log(error); + } +}; + diff --git a/src/utils/calculations/calories-burned.calculations.js b/src/utils/calculations/calories-burned.calculations.js new file mode 100644 index 0000000..43a1282 --- /dev/null +++ b/src/utils/calculations/calories-burned.calculations.js @@ -0,0 +1,40 @@ +// calories burned calculations + +export const calculateSummary = (trackedCaloriesBurned) => { + console.log(trackedCaloriesBurned) + + let dailyAverageCaloriesBurned = 0 + let mostBurned = { + date: "", + caloriesBurned: 0, + activity: "", + } + let totalTrackedDays = new Set() + let totalTrackedActivities = new Set() + + trackedCaloriesBurned.map((trackedActivity) => { + dailyAverageCaloriesBurned += trackedActivity.totalCaloriesBurned + + if (trackedActivity.totalCaloriesBurned >= mostBurned.caloriesBurned) { + mostBurned = { + date: String(trackedActivity.dateTracked), + caloriesBurned: Number(trackedActivity.totalCaloriesBurned), + activity: String(trackedActivity.activity) + } + } + + totalTrackedDays.add(trackedActivity.dateTracked) + totalTrackedActivities.add(trackedActivity.activity) + }) + + return { + dailyAverageCaloriesBurned: dailyAverageCaloriesBurned / totalTrackedDays.size, + mostCaloriesBurned: { + date: mostBurned.date, + caloriesBurned: mostBurned.caloriesBurned, + activity: mostBurned.activity + }, + totalTrackedDays: totalTrackedDays, + totalTrackedActivities: totalTrackedActivities + } +} \ No newline at end of file diff --git a/src/utils/calculations/nutrition-tracker.calculations.js b/src/utils/calculations/nutrition-tracker.calculations.js new file mode 100644 index 0000000..f73ac1d --- /dev/null +++ b/src/utils/calculations/nutrition-tracker.calculations.js @@ -0,0 +1,28 @@ +// nutrition tracker calculations + +export const calculateSummary = (nutritionTrackedDays) => { + const trackedDays = nutritionTrackedDays.length; + + const averageDailyCalories = (nutritionTrackedDays.reduce((totalCalories, { calories }) => { + return totalCalories + Number(calories); + }, 0)) / trackedDays; + + const averageDailyCarbohydrates = (nutritionTrackedDays.reduce((totalCarbohydrates, { macronutrients }) => { + return totalCarbohydrates + Number(macronutrients.carbohydrates); + }, 0)) / trackedDays; + + const averageDailyProtein = (nutritionTrackedDays.reduce((totalProtein, { macronutrients }) => { + return totalProtein + Number(macronutrients.protein); + }, 0)) / trackedDays; + + const averageDailyFat = (nutritionTrackedDays.reduce((totalFat, { macronutrients }) => { + return totalFat + Number(macronutrients.fat); + }, 0)) / trackedDays; + + return { + averageDailyCalories: averageDailyCalories, + averageDailyCarbohydrates: averageDailyCarbohydrates, + averageDailyProtein: averageDailyProtein, + averageDailyFat: averageDailyFat, + } +}; \ No newline at end of file diff --git a/src/utils/calculations/recipes.calculations.js b/src/utils/calculations/recipes.calculations.js new file mode 100644 index 0000000..cefb5fa --- /dev/null +++ b/src/utils/calculations/recipes.calculations.js @@ -0,0 +1,21 @@ +// recipes calculations + +export const calculateIngredientsAfterServingsUpdate = (recipeToUpdate, updatedServings) => { + const originalServings = recipeToUpdate.servings; + + const updatedIngredients = recipeToUpdate.ingredients.map((ingredient) => { + if (ingredient.quantity === null || ingredient.quantity === undefined) { + return ingredient; + } + + console.log(updatedServings / originalServings); + + return { + ...ingredient, + + quantity: (ingredient.quantity * (updatedServings / originalServings)), + } + }); + + return updatedIngredients; +}; \ No newline at end of file diff --git a/src/utils/constants/calories-burned.constants.js b/src/utils/constants/calories-burned.constants.js new file mode 100644 index 0000000..662508d --- /dev/null +++ b/src/utils/constants/calories-burned.constants.js @@ -0,0 +1,12 @@ +// calories-burned constants + +export const DEFAULT_TRACKED_CALORIES_BURNED = [] + +export const DEFAULT_TRACKED_CALORIES_BURNED_SUMMARY = {} + +export const GRAPH_FIELDS = { + date: "Date", + caloriesBurned: "Calories Burned", + dateTitle: "Tracked Days", + caloriesBurnedTitle: "Calories Burned" +} \ No newline at end of file diff --git a/src/utils/constants/chatbot.constants.js b/src/utils/constants/chatbot.constants.js new file mode 100644 index 0000000..38de043 --- /dev/null +++ b/src/utils/constants/chatbot.constants.js @@ -0,0 +1 @@ +// chatbot constants diff --git a/src/utils/constants/nutrient-predictor.constants.js b/src/utils/constants/nutrient-predictor.constants.js new file mode 100644 index 0000000..cc147d9 --- /dev/null +++ b/src/utils/constants/nutrient-predictor.constants.js @@ -0,0 +1,12 @@ +// nutrient-predictor constants + +export const IMAGE_EXTENSIONS = { + png: "png", + jpeg: "jpeg", + jpg: "jpg", +}; + +export const NUTRIENT_PREDICTOR_ENUMS = { + text: "text", + image: "image", +} \ No newline at end of file diff --git a/src/utils/constants/nutrition-tracker.constants.js b/src/utils/constants/nutrition-tracker.constants.js new file mode 100644 index 0000000..a5e2d4b --- /dev/null +++ b/src/utils/constants/nutrition-tracker.constants.js @@ -0,0 +1,20 @@ +// nutrition-tracker constants + +export const DEFAULT_MICRONUTRIENT = { + name: "", + amount: "", + unit: "", +}; + +export const DEFAULT_NUTRITION_TRACKED_DAYS = []; + +export const DEFAULT_NUTRITION_TRACKED_DAYS_SUMMARY = {}; + +export const GRAPH_FIELDS = { + date: "Date", + calories: "Calories", + carbohydrates: "Carbohydrates (g)", + protein: "Protein (g)", + fat: "Fat (g)", + caloriesTitle: "Tracked Calories" +} \ No newline at end of file diff --git a/src/utils/constants/recipes.constants.js b/src/utils/constants/recipes.constants.js new file mode 100644 index 0000000..bb279cf --- /dev/null +++ b/src/utils/constants/recipes.constants.js @@ -0,0 +1,10 @@ +// recipes constants + +export const RECIPES_PER_PAGE = 20; + +export const PAGINATION_BUTTONS = { + next: "NEXT_PAGE", + previous: "PREVIOUS_PAGE", +}; + +export const TIMEOUT_SEC = 10; \ No newline at end of file diff --git a/src/utils/constants/shared.constants.js b/src/utils/constants/shared.constants.js new file mode 100644 index 0000000..aac2fc9 --- /dev/null +++ b/src/utils/constants/shared.constants.js @@ -0,0 +1,169 @@ +// shared constants + +export const BUTTON_TYPE_CLASSES = { + base: "regular-button", + google: "google-sign-in", + inverted: "inverted", +}; + +export const NAV_LINKS = { + headers: { + home: "Home", + chatbot: "Chatbot", + dashboard: "Dashboard", + nutrientPredictor: "Nutrient Predictor", + nutritionTracker: "Nutrition Tracker", + caloriesBurned: "Calories Burned", + recipes: "Recipes", + signedOut: "Sign In / Register", + signedIn: "Sign Out", + website: "tahmidsajin.com", + github: "GitHub", + medium: "Medium", + linkedin: "LinkedIn" + }, + paths: { + signedOut: { + home: "/", + chatbot: "/dashboard", + dashboard: "/dashboard", + nutrientPredictor: "/nutrient-predictor", + nutritionTracker: "/nutrition-tracker", + caloriesBurned: "/calories-burned", + recipes: "/recipes", + auth: "/auth", + }, + signedIn: { + home: "/", + chatbot: "/dashboard-signed-in", + dashboard: "/dashboard-signed-in", + nutrientPredictor: "/nutrient-predictor", + nutritionTracker: "/nutrition-tracker-signed-in", + caloriesBurned: "/calories-burned-signed-in", + recipes: "/recipes", + auth: "/", + } + }, + signedOut: { + section1: [ + { + header: "Home", + path: "/", + }, + { + header: "Chatbot", + path: "/dashboard", + }, + { + header: "Dashboard", + path: "/dashboard", + }, + { + header: "Nutrient Predictor", + path: "/nutrient-predictor", + }, + { + header: "Nutrition Tracker", + path: "/nutrition-tracker", + }, + { + header: "Calories Burned", + path: "/calories-burned", + }, + { + header: "Recipes", + path: "/recipes", + }, + ], + section2: [ + { + header: "Sign In / Register", + path: "/auth" + } + ], + section3: [ + { + header: "tahmidsajin.com", + url: "http://tahmidsajin.com/" + }, + { + header: "GitHub", + url: "https://github.com/tahmid-saj" + }, + { + header: "Medium", + url: "http://tahmidsajin.com/" + }, + { + header: "LinkedIn", + url: "https://ca.linkedin.com/in/tsajin" + }, + ] + }, + signedIn: { + section1: [ + { + header: "Home", + path: "/", + }, + { + header: "Chatbot", + path: "/dashboard-signed-in", + }, + { + header: "Dashboard", + path: "/dashboard-signed-in", + }, + { + header: "Nutrient Predictor", + path: "/nutrient-predictor", + }, + { + header: "Nutrition Tracker", + path: "/nutrition-tracker-signed-in", + }, + { + header: "Calories Burned", + path: "/calories-burned-signed-in", + }, + { + header: "Recipes", + path: "/recipes", + }, + ], + section2: [ + { + header: "Sign Out", + path: "/" + } + ], + section3: [ + { + header: "tahmidsajin.com", + url: "http://tahmidsajin.com/" + }, + { + header: "GitHub", + url: "https://github.com/tahmid-saj" + }, + { + header: "Medium", + url: "http://tahmidsajin.com/" + }, + { + header: "LinkedIn", + url: "https://ca.linkedin.com/in/tsajin" + }, + ] + } +} + +export const COLOR_CODES = { + card: { + infoCard: "#F7F9F9" + }, + paper: { + formPaper: "#EAFAF1", + infoPaper: "#F7F9F9" + } +} \ No newline at end of file diff --git a/src/utils/errors/calories-burned.errors.js b/src/utils/errors/calories-burned.errors.js new file mode 100644 index 0000000..4b518f5 --- /dev/null +++ b/src/utils/errors/calories-burned.errors.js @@ -0,0 +1,27 @@ +// errors on calories burned + +// context +export const errorOnInvalidTrackedDate = () => { + alert("Date cannot be in the future") +} + +// api requests +export const errorOnGetSearchActivity = () => { + alert("Error getting activity") +} + +export const errorOnGetTrackedCaloriesBurned = () => { + alert("Error getting tracked calories burned data") +} + +export const errorOnPostAddActivity = () => { + alert("Error adding activity") +} + +export const errorOnDeleteRemoveActivity = () => { + alert("Error removing activity") +} + +export const errorOnPutTrackedCaloriesBurned = () => { + alert("Error updating tracked calories burned data") +} \ No newline at end of file diff --git a/src/utils/errors/chatbot.errors.js b/src/utils/errors/chatbot.errors.js new file mode 100644 index 0000000..0ded5b8 --- /dev/null +++ b/src/utils/errors/chatbot.errors.js @@ -0,0 +1,9 @@ +// chatbot errors + +export const errorOnInvalidMessageInput = () => { + alert("Invalid message input") +} + +export const errorOnGetChatBotResponse = () => { + alert("Error getting chatbot response") +} \ No newline at end of file diff --git a/src/utils/errors/nutrient-predictor.errors.js b/src/utils/errors/nutrient-predictor.errors.js new file mode 100644 index 0000000..73828b8 --- /dev/null +++ b/src/utils/errors/nutrient-predictor.errors.js @@ -0,0 +1,11 @@ +// nutrient-predictor errors + +// context +export const errorOnInvalidImageType = () => { + alert("Invalid image type"); +}; + +// api requests +export const errorOnGetNutrientPredictions = () => { + alert("Error getting nutrient predictions") +} \ No newline at end of file diff --git a/src/utils/errors/nutrition-tracker.errors.js b/src/utils/errors/nutrition-tracker.errors.js new file mode 100644 index 0000000..7680e8a --- /dev/null +++ b/src/utils/errors/nutrition-tracker.errors.js @@ -0,0 +1,56 @@ +// nutrition-tracker errors + +export const errorOnTrackedDayExists = () => { + alert("Tracked day already exists"); +}; + +export const errorOnInvalidMacronutrientInputs = () => { + alert("Invalid macronutrient inputs"); +}; + +export const errorOnInvalidMicronutrientInput = () => { + alert("Invalid micronutrient inputs"); +}; + +export const errorOnEmptyMicronutrients = () => { + alert("Micronutrients are empty"); +}; + +export const errorOnDayNotTracked = () => { + alert("Day is not being tracked"); +}; + +export const errorOnStartDateBeforeEndDate = () => { + alert("Invalid date on filter") +} + +// api requests + +export const errorOnGetNutritionTrackedDaysData = () => { + alert("Error getting tracked days data"); +}; + +export const errorOnGetNutritionTrackedDaysSummaryData = () => { + alert("Error getting tracked days summary data"); +}; + +export const errorOnPostNutritionTrackedDay = () => { + alert("Error posting nutrition tracked day"); +}; + +export const errorOnDeleteNutritionTrackedDay = () => { + alert("Error removing nutrition tracked day") +} + +export const errorOnPutNutritionTrackedDay = () => { + alert("Error putting nutrition tracked day"); +}; + +export const errorOnPutNutritionTrackedDays = () => { + alert("Error putting nutrition tracked days"); +}; + +export const errorOnPutNutritionTrackedDaysSummary = () => { + alert("Error putting nutrition tracked days summary"); +}; + diff --git a/src/utils/errors/recipes.errors.js b/src/utils/errors/recipes.errors.js new file mode 100644 index 0000000..fb5da3c --- /dev/null +++ b/src/utils/errors/recipes.errors.js @@ -0,0 +1,9 @@ +// errors on recipes + +export const errorOnDisplaySearchedRecipes = (recipeNameSearched) => { + alert(`${recipeNameSearched} could not be found`); +}; + +export const errorOnInvalidSearchedRecipe = () => { + alert("Invalid searched recipe name"); +}; \ No newline at end of file diff --git a/src/utils/errors/user.errors.js b/src/utils/errors/user.errors.js new file mode 100644 index 0000000..7827b6e --- /dev/null +++ b/src/utils/errors/user.errors.js @@ -0,0 +1,17 @@ +// user errors + +export const errorOnCreatingUser = (error) => { + alert(`Error creating the account - ${error.message}`); +}; + +export const errorOnSignIn = (error) => { + alert(`Error signing in - ${error}`) +}; + +export const errorOnEmailAlreadyInUse = () => { + alert("Email already in use"); +}; + +export const errorOnUserCreation = (error) => { + alert(`User creation encountered an error - ${error}`) +}; \ No newline at end of file diff --git a/src/utils/external-js/nutrition-predictor.external.js b/src/utils/external-js/nutrition-predictor.external.js new file mode 100644 index 0000000..eb76839 --- /dev/null +++ b/src/utils/external-js/nutrition-predictor.external.js @@ -0,0 +1,419 @@ +// Copyright 2015 Owen Astrachan, Drew Hilton, Susan Rodger, Robert Duvall +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Created by Nick Parlante +// Updated by Robert Duvall + + +// Represents one pixel in a SimpleImage, supports rgb get/set. +let SimplePixel; +let SimpleImage; + +SimplePixel = function (simple_image, x, y) { + __SimpleImageUtilities.funCheck('SimplePixel', 3, arguments.length); + this.container = simple_image; + this.x = x; + this.y = y; +}; + +SimplePixel.prototype = { + constructor: SimplePixel, + getX: function () { + __SimpleImageUtilities.funCheck('getX', 0, arguments.length); + return this.x; + }, + getY: function () { + __SimpleImageUtilities.funCheck('getY', 0, arguments.length); + return this.y; + }, + getRed: function () { + __SimpleImageUtilities.funCheck('getRed', 0, arguments.length); + return this.container.getRed(this.x, this.y); + }, + getGreen: function () { + __SimpleImageUtilities.funCheck('getGreen', 0, arguments.length); + return this.container.getGreen(this.x, this.y); + }, + getBlue: function () { + __SimpleImageUtilities.funCheck('getBlue', 0, arguments.length); + return this.container.getBlue(this.x, this.y); + }, + getAlpha: function () { + __SimpleImageUtilities.funCheck('getAlpha', 0, arguments.length); + return this.container.getAlpha(this.x, this.y); + }, + setRed: function (val) { + __SimpleImageUtilities.funCheck('setRed', 1, arguments.length); + this.container.setRed(this.x, this.y, val); + }, + setGreen: function (val) { + __SimpleImageUtilities.funCheck('setGreen', 1, arguments.length); + this.container.setGreen(this.x, this.y, val); + }, + setBlue: function (val) { + __SimpleImageUtilities.funCheck('setBlue', 1, arguments.length); + this.container.setBlue(this.x, this.y, val); + }, + setAlpha: function (val) { + __SimpleImageUtilities.funCheck('setAlpha', 1, arguments.length); + this.container.setAlpha(this.x, this.y, val); + }, + setAllFrom: function (pixel) { + __SimpleImageUtilities.funCheck('setAllFrom', 1, arguments.length); + this.setRed(pixel.getRed()); + this.setGreen(pixel.getGreen()); + this.setBlue(pixel.getBlue()); + this.setAlpha(pixel.getAlpha()); + }, + toString: function () { + return 'r:' + this.getRed() + ' g:' + this.getGreen() + ' b:' + this.getBlue(); + }, + // Render pixel as string + getString: function () { + return this.toString(); + } +}; + + +// Note there is an Image built in, so don't use that name. +// A SimpleImage can be created with a url, size, or an existing htmlImage or canvas +SimpleImage = function () { + if (arguments.length < 0 || arguments.length > 2) { + __SimpleImageUtilities.funCheck('SimpleImage', 1, arguments.length); + return null; + } + // function map for to support "overloaded constructor" + var funMap = [ + function () { + return __SimpleImageUtilities.EMPTY_IMAGE; + }, + function (source) { + if (source instanceof HTMLImageElement) { + return source; + } else if (typeof source == 'string') { + return __SimpleImageUtilities.makeHTMLImageFromURL(source, this); + } else if (source instanceof HTMLInputElement && source.type == 'file') { + return __SimpleImageUtilities.makeHTMLImageFromInput(source.files[0], this); + } else if (source instanceof SimpleImage) { + return source.canvas; + } else if (source instanceof HTMLCanvasElement) { + return source; + } else { + __SimpleImageUtilities.throwError('Unrecognized value used to create a SimpleImage: ' + source); + } + }, + function (width, height) { + if (width > 0 && height > 0) { + return __SimpleImageUtilities.makeHTMLImageFromSize(width, height); + } else { + __SimpleImageUtilities.throwError('Unable to create a SimpleImage with a negative width or height [' + width + 'x' + height + ']'); + } + } + ]; + + // call appropriate constructor + var htmlImage = funMap[arguments.length].apply(this, arguments); + // actual content is backed by an invisible canvas + this.canvas = __SimpleImageUtilities.makeHTMLCanvas('SimpleImageCanvas'); + this.canvas.style.display = 'none'; + this.context = this.canvas.getContext('2d'); + // when image is loaded, it will fill this in + this.imageData = null; + // check to see if we can complete the constructor now instead of waiting + if (htmlImage != null && (htmlImage instanceof HTMLCanvasElement || htmlImage.complete)) { + this.__init(htmlImage); + } + this.ACCEPTED_FILES = 'image.*'; +} + + +SimpleImage.prototype = { + constructor: SimpleImage, + complete: function () { + return this.imageData != null; + }, + getWidth: function () { + __SimpleImageUtilities.funCheck('getWidth', 0, arguments.length); + return this.width; + }, + getHeight: function () { + __SimpleImageUtilities.funCheck('getHeight', 0, arguments.length); + return this.height; + }, + getRed: function (x, y) { + __SimpleImageUtilities.funCheck('getRed', 2, arguments.length); + return this.imageData.data[this.__getIndex('getRed', x, y)]; + }, + getGreen: function (x, y) { + __SimpleImageUtilities.funCheck('getGreen', 2, arguments.length); + return this.imageData.data[this.__getIndex('getGreen', x, y) + 1]; + }, + getBlue: function (x, y) { + __SimpleImageUtilities.funCheck('getBlue', 2, arguments.length); + return this.imageData.data[this.__getIndex('getBlue', x, y) + 2]; + }, + getAlpha: function (x, y) { + __SimpleImageUtilities.funCheck('getAlpha', 2, arguments.length); + return this.imageData.data[this.__getIndex('getAlpha', x, y) + 3]; + }, + // Changes to the pixel write back to the image. + getPixel: function (x, y) { + __SimpleImageUtilities.funCheck('getPixel', 2, arguments.length); + __SimpleImageUtilities.rangeCheck(x, 0, this.getWidth(), 'getPixel', 'x', 'wide'); + __SimpleImageUtilities.rangeCheck(y, 0, this.getHeight(), 'getPixel', 'y', 'tall'); + return new SimplePixel(this, x, y); + }, + + setRed: function (x, y, value) { + __SimpleImageUtilities.funCheck('setRed', 3, arguments.length); + this.imageData.data[this.__getIndex('getRed', x, y)] = __SimpleImageUtilities.clamp(value); + }, + setGreen: function (x, y, value) { + __SimpleImageUtilities.funCheck('setGreen', 3, arguments.length); + this.imageData.data[this.__getIndex('getGreen', x, y) + 1] = __SimpleImageUtilities.clamp(value); + }, + setBlue: function (x, y, value) { + __SimpleImageUtilities.funCheck('setBlue', 3, arguments.length); + this.imageData.data[this.__getIndex('getBlue', x, y) + 2] = __SimpleImageUtilities.clamp(value); + }, + setAlpha: function (x, y, value) { + __SimpleImageUtilities.funCheck('setAlpha', 3, arguments.length); + this.imageData.data[this.__getIndex('getAlpha', x, y) + 3] = __SimpleImageUtilities.clamp(value); + }, + setPixel: function (x, y, pixel) { + __SimpleImageUtilities.funCheck('setPixel', 3, arguments.length); + __SimpleImageUtilities.rangeCheck(x, 0, this.getWidth(), 'setPixel', 'x', 'wide'); + __SimpleImageUtilities.rangeCheck(y, 0, this.getHeight(), 'setPixel', 'y', 'tall'); + this.setRed(x, y, pixel.getRed()); + this.setBlue(x, y, pixel.getBlue()); + this.setGreen(x, y, pixel.getGreen()); + this.setAlpha(x, y, pixel.getAlpha()); + }, + // Scales contents of SimpleIage to the given size + setSize: function (width, height) { + __SimpleImageUtilities.funCheck('setSize', 2, arguments.length); + width = Math.floor(width); + height = Math.floor(height); + if (width > 0 && height > 0) { + // make sure we have the most current changes + __SimpleImageUtilities.flush(this.context, this.imageData); + this.imageData = __SimpleImageUtilities.changeSize(this.canvas, width, height); + this.width = width; + this.height = height; + this.canvas.width = width; + this.canvas.height = height; + } + else { + __SimpleImageUtilities.throwError('You tried to set the size of a SimpleImage to a negative width or height [' + width + 'x' + height + ']'); + } + }, + // Draws to the given canvas, setting its size to match SimpleImage's size + drawTo: function (toCanvas) { + if (this.imageData != null) { + __SimpleImageUtilities.flush(this.context, this.imageData); + toCanvas.width = this.getWidth(); + toCanvas.height = this.getHeight(); + toCanvas.getContext('2d').drawImage(this.canvas, 0, 0, toCanvas.width, toCanvas.height); + } + else { + var myself = this; + setTimeout(function() { + myself.drawTo(toCanvas); + }, 100); + } + }, + // Export an image as an linear array of pixels that can be iterated over + toArray: function () { + __SimpleImageUtilities.funCheck('toArray', 0, arguments.length); + var array = new Array(); + // nip 2012-7 + // 1. simple-way (this is as good or faster in various browser tests) + // var array = new Array(this.getWidth() * this.getHeight()); + // 2. change to cache-friendly y/x ordering + // Non-firefox browsers may benefit + for (var y = 0; y < this.getHeight(); y++) { + for (var x = 0; x < this.getWidth(); x++) { + //array[i++] = new SimplePixel(this, x, y); // 2. + array.push(new SimplePixel(this, x, y)); // 1. + } + } + return array; + }, + // Support iterator within for loops (eventually) + values: function() { + __SimpleImageUtilities.funCheck('values', 0, arguments.length); + return this.toArray(); + }, + // Better name than values if we have to use it + pixels: function() { + return this.values(); + }, + + // Private methods: should not be called publicly, but it should not hurt if it is + // Completes the construction of this object once the htmlImage is loaded + __init: function (img) { + try { + this.id = img.id; + // this is a hack to make three different cases work together: + // - small empty image, thumbnail images, and canvases + this.width = ('naturalWidth' in img) ? Math.max(img.naturalWidth, img.width) : img.width; + this.height = ('naturalHeight' in img) ? Math.max(img.naturalHeight, img.height) : img.height; + this.canvas.width = this.width; + this.canvas.height = this.height; + // are we copying an already loaded image or drawing it fresh + if (img instanceof HTMLCanvasElement) { + var canvasData = img.getContext('2d').getImageData(0, 0, this.width, this.height); + this.context.putImageData(canvasData, 0, 0); + } + else { + this.context.drawImage(img, 0, 0, this.width, this.height); + } + this.imageData = this.context.getImageData(0, 0, this.width, this.height); + } + catch (err) { + console.log(err); + __SimpleImageUtilities.throwError('The name you used to create a SimpleImage was not correct: ' + img.id); + } + }, + // computes index into 1-d array, and checks correctness of x,y values + __getIndex: function (funName, x, y) { + __SimpleImageUtilities.rangeCheck(x, 0, this.getWidth(), funName, 'x', 'wide'); + __SimpleImageUtilities.rangeCheck(y, 0, this.getHeight(), funName, 'y', 'tall'); + return (Math.floor(x) + Math.floor(y) * this.getWidth()) * 4; + } +}; + + +// Private helper functions, add __ to reduce chance they will conflict with anyone else's method names +var __SimpleImageUtilities = (function () { + // private globals + // image needed to seed "sized" image + var EMPTY_IMAGE_DATA = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAQAAAAnZu5uAAAAAXNSR0IArs4c6QAAABVJREFUeJxiYPgPhyQwAQAAAP//AwCgshjoJhZxhgAAAABJRU5ErkJggg=='; + // number of canvases created to hold images + var globalCanvasCount = 0; + // load image by wrapping it in an HTML element + function makeHTMLImage (url, name, simpleImage, loadFunc) { + if (loadFunc == null) { + loadFunc = function() { + simpleImage.__init(this); + console.log('loaded image: ' + simpleImage.id); + } + } + var img = new Image(); + img.onload = loadFunc; + img.src = url; + img.id = name; + img.style.display = 'none'; + return img; + } + + // public utility functions + return { + // make a blank image so it is cached for future uses + EMPTY_IMAGE: makeHTMLImage(EMPTY_IMAGE_DATA, 'EMPTY', null, function () {}), + + // create a canvas element + makeHTMLCanvas: function (prefix) { + var canvas = document.createElement('canvas'); + canvas.id = prefix + globalCanvasCount; + canvas.style.display = 'none'; + canvas.innerHTML = 'Your browser does not support HTML5.' + globalCanvasCount++; + return canvas; + }, + + // get image from uploaded file input + makeHTMLImageFromInput: function (file, simpleImage) { + console.log('creating image: ' + file.name); + var reader = new FileReader(); + reader.onload = function() { + makeHTMLImage(this.result, file.name.substr(file.name.lastIndexOf('/') + 1), simpleImage); + } + reader.readAsDataURL(file); + return null; + }, + + // get image from a relative URL + makeHTMLImageFromURL: function (url, simpleImage) { + var name = url.substr(0, url.indexOf(';')); + console.log('creating image: ' + name); + if (url.substr(0, 4) != 'http') { + return makeHTMLImage(url, name, simpleImage); + } + else { + // does not work --- loading image from URL taints the canvas so we cannot use it :( + __SimpleImageUtilities.throwError('Unfortunately you cannot create a SimpleImage directly from a URL: ' + url); + } + }, + + // create an empty image of the given size + makeHTMLImageFromSize: function (width, height) { + console.log('creating image: ' + width + 'x' + height); + var img = __SimpleImageUtilities.EMPTY_IMAGE.cloneNode(true); + img.width = width; + img.height = height; + return img; + }, + + // set size of the image to the given values, scaling the pixels + changeSize: function (canvasOld, newWidth, newHeight) { + var canvasNew = __SimpleImageUtilities.makeHTMLCanvas('setSize_'); + canvasNew.width = newWidth; + canvasNew.height = newHeight; + // draw old canvas to new canvas + var contextNew = canvasNew.getContext('2d'); + contextNew.drawImage(canvasOld, 0, 0, newWidth, newHeight); + return contextNew.getImageData(0, 0, newWidth, newHeight); + }, + + // clamp values to be in the range 0..255 + clamp: function (value) { + return Math.max(0, Math.min(Math.floor(value), 255)); + }, + + // push accumulated local changes out to the screen + flush: function (context, imageData) { + if (imageData != null) { + context.putImageData(imageData, 0, 0, 0, 0, imageData.width, imageData.height); + } + }, + + // call this to abort with a message + throwError: function (message) { + throw new Error(message); + }, + + // called from user-facing functions to check number of arguments + funCheck: function (funcName, expectedLen, actualLen) { + if (expectedLen != actualLen) { + var s1 = (actualLen == 1) ? '' : 's'; // pluralize correctly + var s2 = (expectedLen == 1) ? '' : 's'; + var message = 'You tried to call ' + funcName + ' with ' + actualLen + ' value' + s1 + + ', but it expects ' + expectedLen + ' value' + s2 + '.'; + // someday: think about "values" vs. "arguments" here + __SimpleImageUtilities.throwError(message); + } + }, + + // called from user-facing functions to check if given value is valid + rangeCheck: function (value, low, high, funName, coordName, size) { + if (value < low || value >= high) { + var message = 'You tried to call ' + funName + ' for a pixel with ' + coordName + '-coordinate of ' + value + + ' in an image that is only ' + high + ' pixels ' + size + + ' (valid ' + coordName + ' coordinates are ' + low + ' to ' + (high-1) + ').'; + __SimpleImageUtilities.throwError(message); + } + } + }; +})(); \ No newline at end of file diff --git a/src/utils/firebase/firebase.utils.js b/src/utils/firebase/firebase.utils.js new file mode 100644 index 0000000..6728f03 --- /dev/null +++ b/src/utils/firebase/firebase.utils.js @@ -0,0 +1,132 @@ +import { initializeApp } from 'firebase/app'; + +import { + getAuth, + signInWithRedirect, + signInWithPopup, + GoogleAuthProvider, + createUserWithEmailAndPassword, + signInWithEmailAndPassword, + signOut, + onAuthStateChanged, +} from 'firebase/auth'; + +import { + getFirestore, + doc, + getDoc, + setDoc, + collection, + writeBatch, + query, + getDocs, +} from 'firebase/firestore'; + +import { errorOnCreatingUser } from '../errors/user.errors'; + +// Your web app's Firebase configuration +const firebaseConfig = { + apiKey: process.env.REACT_APP_FIREBASE_API_KEY, + authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN, + projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID, + storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET, + messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENTER_ID, + appId: process.env.REACT_APP_FIREBASE_APP_ID, +}; + +// Initialize Firebase +const firebaseApp = initializeApp(firebaseConfig); + +const googleProvider = new GoogleAuthProvider(); + +googleProvider.setCustomParameters({ + prompt: "select_account" +}); + +export const auth = getAuth(); +export const signInWithGooglePopup = () => + signInWithPopup(auth, googleProvider); +export const signInWithGoogleRedirect = () => + signInWithRedirect(auth, googleProvider); + +export const db = getFirestore(); + +export const addCollectionAndDocuments = async ( + collectionKey, + objectsToAdd, + field +) => { + const collectionRef = collection(db, collectionKey); + const batch = writeBatch(db); + + objectsToAdd.forEach((object) => { + const docRef = doc(collectionRef, object.title.toLowerCase()); + batch.set(docRef, object); + }); + + await batch.commit(); + console.log('done'); +}; + +export const getCategoriesAndDocuments = async () => { + const collectionRef = collection(db, 'categories'); + const q = query(collectionRef); + + const querySnapshot = await getDocs(q); + const categoryMap = querySnapshot.docs.reduce((acc, docSnapshot) => { + const { title, items } = docSnapshot.data(); + acc[title.toLowerCase()] = items; + return acc; + }, {}); + + return categoryMap; +}; + +export const createUserDocumentFromAuth = async ( + userAuth, + additionalInformation = {} +) => { + if (!userAuth) return; + + const userDocRef = doc(db, 'users', userAuth.uid); + + const userSnapshot = await getDoc(userDocRef); + + if (!userSnapshot.exists()) { + const { displayName, email } = userAuth; + const createdAt = new Date(); + + try { + await setDoc(userDocRef, { + displayName, + email, + createdAt, + ...additionalInformation, + }); + } catch (error) { + errorOnCreatingUser(error); + } + } + + return userDocRef; +}; + +export const createAuthUserWithEmailAndPassword = async (email, password) => { + if (!email || !password) return; + + return await createUserWithEmailAndPassword(auth, email, password); +}; + +export const signInAuthUserWithEmailAndPassword = async (email, password) => { + if (!email || !password) return; + + return await signInWithEmailAndPassword(auth, email, password); +}; + +export const signOutUser = async () => await signOut(auth); + +export const onAuthStateChangedListener = (callback) => { + if (!callback) return; + + return onAuthStateChanged(auth, callback); +} \ No newline at end of file diff --git a/src/utils/validations/calories-burned.validations.js b/src/utils/validations/calories-burned.validations.js new file mode 100644 index 0000000..16df957 --- /dev/null +++ b/src/utils/validations/calories-burned.validations.js @@ -0,0 +1,46 @@ +import { errorOnInvalidTrackedDate } from "../errors/calories-burned.errors" + +// calories burned validations + +// context + +export const validateSearchActivity = (trackedDayInfo) => { + // number + if (!(/^[0-9]*$/.test(String(trackedDayInfo.weightPounds))) || Number(trackedDayInfo.weightPounds) < 0 || + !(/^[0-9]*$/.test(String(trackedDayInfo.durationMinutes))) || Number(trackedDayInfo.durationMinutes) < 0) { + return true + } + + return false +} + +export const validateAddTrackedActivityDate = (trackedDayInfo) => { + const today = new Date() + + if (trackedDayInfo.dateTracked && trackedDayInfo.dateTracked > today) { + errorOnInvalidTrackedDate() + return true + } + + return false +} + +export const validateFilterActivityDates = (filterConditions) => { + const today = new Date() + + // number + if (!(/^[0-9]*$/.test(String(filterConditions.durationMinutes))) || Number(filterConditions.durationMinutes) < 0) { + return true + } + + if (filterConditions.dateTracked && filterConditions.dateTracked > today) { + errorOnInvalidTrackedDate() + return true + } + + return false +} + +export const validateRemoveActivityDate = (activityId) => { + return false +} \ No newline at end of file diff --git a/src/utils/validations/chatbot.validation.js b/src/utils/validations/chatbot.validation.js new file mode 100644 index 0000000..6460157 --- /dev/null +++ b/src/utils/validations/chatbot.validation.js @@ -0,0 +1,12 @@ +import { errorOnInvalidMessageInput } from "../errors/chatbot.errors" + +// chatbot validation + +export const validateChatBotMessageInput = (messageInput) => { + if (messageInput === "") { + errorOnInvalidMessageInput() + return true + } + + return false +} \ No newline at end of file diff --git a/src/utils/validations/nutrient-predictor.validations.js b/src/utils/validations/nutrient-predictor.validations.js new file mode 100644 index 0000000..d956850 --- /dev/null +++ b/src/utils/validations/nutrient-predictor.validations.js @@ -0,0 +1,32 @@ +// import { readChunk } from "read-chunk"; +// import imageType, { minimumBytes } from "image-type"; + +import { IMAGE_EXTENSIONS } from "../constants/nutrient-predictor.constants"; +import { errorOnInvalidImageType } from "../errors/nutrient-predictor.errors"; + +// validation functions + +export const validateImgPath = (imgPath) => { + // const buffer = await readChunk(imgPath, { length: minimumBytes }); + // const imgType = await imageType(buffer); + + // if (!(imgType === "image/png" || imgType === "png" || + // imgType === "image/jpeg" || imgType === "jpeg" || + // imgType === "image/jpg" || imgType === "jpg")) { + // console.log("Invalid image type", imgPath.type); + // return true; + // } + + const paths = imgPath.split("."); + + if (!(imgPath.split(".")[paths.length - 1] === IMAGE_EXTENSIONS.png || + imgPath.split(".")[paths.length - 1] === IMAGE_EXTENSIONS.jpeg || + imgPath.split(".")[paths.length - 1] === IMAGE_EXTENSIONS.jpg)) { + + errorOnInvalidImageType(); + + return true; + } + + return false; +}; \ No newline at end of file diff --git a/src/utils/validations/nutrition-tracker.validations.js b/src/utils/validations/nutrition-tracker.validations.js new file mode 100644 index 0000000..2f53b03 --- /dev/null +++ b/src/utils/validations/nutrition-tracker.validations.js @@ -0,0 +1,156 @@ +import { errorOnTrackedDayExists, errorOnInvalidMacronutrientInputs, + errorOnInvalidMicronutrientInput, errorOnEmptyMicronutrients, + errorOnDayNotTracked, errorOnStartDateBeforeEndDate } from "../errors/nutrition-tracker.errors"; + +// nutrition tracker validation functions + +export const validateAddDayTracked = (nutritionTrackedDays, trackedDayInfo) => { + // check that trackedDayInfo's day doesn't exist in nutritionTrackedDays + const trackedDayExists = nutritionTrackedDays.find((nutritionTrackedDay) => { + return nutritionTrackedDay.dateTracked === trackedDayInfo.dateTracked; + }); + + if (trackedDayExists) { + errorOnTrackedDayExists(); + + return true; + } + + // check if macronutrients data types are valid + if (!(/^[0-9]*$/.test(String(trackedDayInfo.calories))) || + Number(trackedDayInfo.calories) < 0 || + !(/^[0-9]*$/.test(String(trackedDayInfo.macronutrients.carbohydrates))) || + Number(trackedDayInfo.macronutrients.carbohydrates) < 0 || + !(/^[0-9]*$/.test(String(trackedDayInfo.macronutrients.protein))) || + Number(trackedDayInfo.macronutrients.protein) < 0 || + !(/^[0-9]*$/.test(String(trackedDayInfo.macronutrients.fat))) || + Number(trackedDayInfo.macronutrients.fat) < 0) { + + errorOnInvalidMacronutrientInputs(); + + return true; + } + + // check if micronutrients are valid + // check if micronutrients data types are valid + const invalidMicronutrients = trackedDayInfo.micronutrients.find(micronutrient => { + if (String(micronutrient.name).length > 50 || String(micronutrient.unit).length > 5 ) { + + errorOnInvalidMicronutrientInput(); + + return true; + } + + if (!(/^[0-9]*$/.test(String(micronutrient.amount))) || + Number(micronutrient.amount) <= 0) { + + errorOnInvalidMicronutrientInput(); + + return true; + } + + return false; + }); + + if (invalidMicronutrients) return true; + + // check if micronutrients are not empty + const emptyMicronutrients = trackedDayInfo.micronutrients.find(micronutrient => { + if (String(micronutrient.name) === "" || String(micronutrient.amount) === "" || + String(micronutrient.unit) === "") { + + errorOnEmptyMicronutrients(); + + return true; + } + + return false; + }); + + if (emptyMicronutrients) return true; + + return false; +}; + +export const validateUpdateDayTracked = (nutritionTrackedDays, updatedTrackedDayInfo) => { + // check that updatedTrackedDayInfo exists in nutritionTrackedDays + const updatedTrackedDayExists = nutritionTrackedDays.find(nutritionTrackedDay => { + return nutritionTrackedDay.dateTracked === updatedTrackedDayInfo.dateTracked; + }); + + if (!updatedTrackedDayExists) { + + errorOnDayNotTracked(); + + return true; + } + + // check if macronutrients data types are valid + if (!(/^[0-9]*$/.test(String(updatedTrackedDayInfo.calories))) || + Number(updatedTrackedDayInfo.calories) < 0 || + !(/^[0-9]*$/.test(String(updatedTrackedDayInfo.macronutrients.carbohydrates))) || + Number(updatedTrackedDayInfo.macronutrients.carbohydrates) < 0 || + !(/^[0-9]*$/.test(String(updatedTrackedDayInfo.macronutrients.protein))) || + Number(updatedTrackedDayInfo.macronutrients.protein) < 0 || + !(/^[0-9]*$/.test(String(updatedTrackedDayInfo.macronutrients.fat))) || + Number(updatedTrackedDayInfo.macronutrients.fat) < 0) { + + errorOnInvalidMacronutrientInputs(); + + return true; + } + + // check if micronutrients are valid + // check if micronutrients data types are valid + const invalidMicronutrients = updatedTrackedDayInfo.micronutrients.find(micronutrient => { + if (String(micronutrient.name).length > 50 || String(micronutrient.unit).length > 5 ) { + + errorOnInvalidMicronutrientInput(); + + return true; + } + + if (!(/^[0-9]*$/.test(String(micronutrient.amount))) || + Number(micronutrient.amount) <= 0) { + + errorOnInvalidMicronutrientInput(); + + return true; + } + + return false; + }); + + if (invalidMicronutrients) return true; + + // check if micronutrients are not empty + const emptyMicronutrients = updatedTrackedDayInfo.micronutrients.find(micronutrient => { + if (String(micronutrient.name) === "" || String(micronutrient.amount) === "" || + String(micronutrient.unit) === "") { + + errorOnEmptyMicronutrients(); + + return true; + } + + return false; + }); + + if (emptyMicronutrients) return true; + + return false; +}; + +export const validateFilterNutritionTrackedDays = (filterConditions) => { + // validating if startDate > endDate + if (filterConditions.filterStartDate && filterConditions.filterEndDate && filterConditions.filterStartDate > filterConditions.filterEndDate) { + errorOnStartDateBeforeEndDate() + return true + } + + return false +} + +export const validateRemoveNutritionTrackedDay = (trackedDay) => { + return false +} \ No newline at end of file diff --git a/src/utils/validations/recipes.validations.js b/src/utils/validations/recipes.validations.js new file mode 100644 index 0000000..35e13fe --- /dev/null +++ b/src/utils/validations/recipes.validations.js @@ -0,0 +1,14 @@ +// validations for recipes + +import { errorOnInvalidSearchedRecipe } from "../errors/recipes.errors"; + +export const validateRecipeNameSearched = (recipeNameSearched) => { + if (!/\S/.test(recipeNameSearched)) { + + errorOnInvalidSearchedRecipe(); + + return true; + } + + return false; +}; \ No newline at end of file