From 63a39582056b98ee1fca671f9f05974962b3460b Mon Sep 17 00:00:00 2001 From: 33tm <80350771+33tm@users.noreply.github.com> Date: Fri, 6 Sep 2024 21:39:37 -0700 Subject: [PATCH 01/10] remove inconsistent underlining --- client/src/components/classes/DashboardBlurb.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/classes/DashboardBlurb.tsx b/client/src/components/classes/DashboardBlurb.tsx index 562004a7..cb517403 100644 --- a/client/src/components/classes/DashboardBlurb.tsx +++ b/client/src/components/classes/DashboardBlurb.tsx @@ -92,7 +92,7 @@ export default function DashboardBlurb(props: DashboardBlurbProps) { setIncludeCompleted(!includeCompleted)}> {includeCompleted ? 'Hide completed' : 'Show completed'} - + See more in Upcoming From b52d97dd82c66ea66f2e61aef5d6508aa52960ab Mon Sep 17 00:00:00 2001 From: 33tm <80350771+33tm@users.noreply.github.com> Date: Sat, 7 Sep 2024 22:02:24 -0700 Subject: [PATCH 02/10] period action button --- client/src/components/schedule/Period.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/src/components/schedule/Period.tsx b/client/src/components/schedule/Period.tsx index 0db0a20d..55d7c374 100644 --- a/client/src/components/schedule/Period.tsx +++ b/client/src/components/schedule/Period.tsx @@ -108,6 +108,10 @@ export default function Period(props: PeriodProps) { /> )} + + ); } From dd055746631cf6364de9833c847c26a62f126cb1 Mon Sep 17 00:00:00 2001 From: Leo Jeong <80350771+33tm@users.noreply.github.com> Date: Sun, 15 Sep 2024 19:42:23 -0700 Subject: [PATCH 03/10] consistency between clickable elements --- client/src/components/lists/PillClubComponent.tsx | 2 +- client/src/components/schedule/Period.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/components/lists/PillClubComponent.tsx b/client/src/components/lists/PillClubComponent.tsx index 91cd5169..8884309f 100644 --- a/client/src/components/lists/PillClubComponent.tsx +++ b/client/src/components/lists/PillClubComponent.tsx @@ -9,7 +9,7 @@ export default function PillClubComponent(props: Club & {id: string}) { return ( <> - setModal(true)}> + setModal(true)}> {name} diff --git a/client/src/components/schedule/Period.tsx b/client/src/components/schedule/Period.tsx index 55d7c374..cbe89fad 100644 --- a/client/src/components/schedule/Period.tsx +++ b/client/src/components/schedule/Period.tsx @@ -53,7 +53,7 @@ export default function Period(props: PeriodProps) { <>

{id ? ( - + {name} ) : name} @@ -80,7 +80,7 @@ export default function Period(props: PeriodProps) { {({open}) => (<> {header} - + {note} @@ -109,7 +109,7 @@ export default function Period(props: PeriodProps) { )} - From 202110575cbc46395f5537237b9423142df3c763 Mon Sep 17 00:00:00 2001 From: Leo Jeong <80350771+33tm@users.noreply.github.com> Date: Wed, 25 Sep 2024 11:51:09 -0700 Subject: [PATCH 04/10] format errors --- client/src/components/layout/ErrorBoundary.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/layout/ErrorBoundary.tsx b/client/src/components/layout/ErrorBoundary.tsx index cf1eb107..527745bf 100644 --- a/client/src/components/layout/ErrorBoundary.tsx +++ b/client/src/components/layout/ErrorBoundary.tsx @@ -20,8 +20,8 @@ export default class ErrorBoundary extends Component

WATT has crashed!

-
-                        {this.state.error.name} {this.state.error.message}
+                    
+                        {this.state.error.name}: {this.state.error.message}
                     
                         {this.state.error.stack}

From 5f1631bf21a68564283d484ce7a6a6a3186749ed Mon Sep 17 00:00:00 2001
From: Leo Jeong <80350771+33tm@users.noreply.github.com>
Date: Wed, 25 Sep 2024 12:14:41 -0700
Subject: [PATCH 05/10] prevent note collision with action

---
 client/src/components/schedule/Period.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/client/src/components/schedule/Period.tsx b/client/src/components/schedule/Period.tsx
index cbe89fad..59575e10 100644
--- a/client/src/components/schedule/Period.tsx
+++ b/client/src/components/schedule/Period.tsx
@@ -109,7 +109,7 @@ export default function Period(props: PeriodProps) {
                 
)} - From 0c9818a1459ebe7f73bcaabc18d722a91325bed4 Mon Sep 17 00:00:00 2001 From: Leo Jeong <80350771+33tm@users.noreply.github.com> Date: Fri, 27 Sep 2024 19:45:05 -0700 Subject: [PATCH 06/10] update firestore emulator presets for menu --- functions/presets/firebase-export-metadata.json | 4 ++-- .../all_namespaces_all_kinds.export_metadata | Bin 52 -> 52 bytes .../all_namespaces/all_kinds/output-0 | Bin 377 -> 501 bytes .../firestore_export.overall_export_metadata | Bin 95 -> 95 bytes 4 files changed, 2 insertions(+), 2 deletions(-) diff --git a/functions/presets/firebase-export-metadata.json b/functions/presets/firebase-export-metadata.json index 7d2faf70..c8308945 100644 --- a/functions/presets/firebase-export-metadata.json +++ b/functions/presets/firebase-export-metadata.json @@ -1,7 +1,7 @@ { - "version": "10.7.1", + "version": "13.16.0", "firestore": { - "version": "1.14.3", + "version": "1.19.8", "path": "firestore_export", "metadata_file": "firestore_export/firestore_export.overall_export_metadata" }, diff --git a/functions/presets/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata b/functions/presets/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata index 3c55a1932d53c0d3c296ce0fe6733f5110b8cf13..bab799d2ad7b7ae99a7ce0e019d36617442b9925 100644 GIT binary patch delta 36 ncmXppnII~E>GAPf=bm&hOSJumFobxx7=$?TOG^q$OLPqYKqwE3 delta 36 ncmXppnIJ0P-Ls%IxE4ga55IH#0>F-tCsCndG4F1<7_uRO7&q==7Ohy}z^V#!U-E9I#Y o=Ma_xGbd{>s;Zgj8d&NYS%erESs9yK8JO#tSr|q&F`{V#0D?Xr@c;k- delta 9 Qcmey${F7;7-|Go|02vhp8~^|S diff --git a/functions/presets/firestore_export/firestore_export.overall_export_metadata b/functions/presets/firestore_export/firestore_export.overall_export_metadata index c4a259a52b91ade811a7d8149bdbd8f02540fb9e..cab64b9f6171714eccb046c12c00223af6ffce92 100644 GIT binary patch delta 38 tcma!#=h>0#;>XCqXuPoB;PgZuX>R?*oSgXV%)FFh{fS1JEG!CNnE~hB3%~#X delta 38 tcma!#=h>0#;>XCqXe`IZ<~ET>nmZ;jCnr8TGcTn$W}=ZM3$wycCIG;H3Sj^M From c06f0209abfd57de2ed748414970d89ab436be4f Mon Sep 17 00:00:00 2001 From: Leo Jeong <80350771+33tm@users.noreply.github.com> Date: Fri, 27 Sep 2024 23:53:59 -0700 Subject: [PATCH 07/10] menu function --- functions/src/index.ts | 1 + functions/src/menu.ts | 87 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 functions/src/menu.ts diff --git a/functions/src/index.ts b/functions/src/index.ts index ef187590..784d3550 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -1,4 +1,5 @@ export {api} from './api'; +export {menu} from './menu'; export {sgyauth} from './sgyauth'; export * as sgyfetch from './sgyfetch'; export * as weather from './weather'; diff --git a/functions/src/menu.ts b/functions/src/menu.ts new file mode 100644 index 00000000..b17f37a7 --- /dev/null +++ b/functions/src/menu.ts @@ -0,0 +1,87 @@ +import * as functions from 'firebase-functions'; +import admin from './util/adminInit'; + +import { DateTime } from 'luxon'; +import { getAlternates } from './util/apiUtil'; +import { getSchedule } from '@watt/shared/util/schedule'; +import { SCHOOL_START, SCHOOL_END } from '@watt/shared/data/schedule'; + + +const firestore = admin.firestore(); + +export const menu = functions.https.onCall(async () => { + const now = DateTime.now().setZone('America/Los_Angeles'); + + if (now < SCHOOL_START || now > SCHOOL_END) { + await firestore.collection('gunn').doc('menu').set({ + timestamp: new Date().toISOString(), + menu: {} + }); + return; + } + + const current = (await firestore.collection('gunn').doc('menu').get()).data()!; + if (DateTime.fromISO(current.timestamp).plus({ week: 1 }) > now) + return; + + const { daysInMonth, month, year } = now.plus({ week: 1 }); + const { alternates } = await getAlternates(); + const nutrislice = 'https://pausd.api.nutrislice.com/menu/api'; + const nutrition = new Map(); + + async function getNutrition(day: number) { + return await Promise.all(['breakfast', 'lunch'].map(async meal => { + const { days } = await fetch(`${nutrislice}/weeks/school/henry-m-gunn-hs/menu-type/${meal}/${year}/${month}/${day}`) + .then(res => res.json()) + .catch(() => []); + if (!days) return; + const items = days + .flatMap((day: any) => day.menu_items) + .map((items: any) => items.food) + .filter(Boolean); + for (const item of items) { + nutrition.set(item.name, { + serving: Object + .values(item.serving_size_info) + .every(x => !x) ? null : item.serving_size_info, + nutrition: Object + .values(item.rounded_nutrition_info) + .every(x => !x) ? null : item.rounded_nutrition_info, + ingredients: item.ingredients || null + }); + } + })); + } + + async function getMenu(date: DateTime) { + const { year, month, day } = date + const [brunch, lunch] = await Promise.all(['breakfast', 'lunch'].map(async meal => { + const { menu_items } = await fetch(`${nutrislice}/digest/school/henry-m-gunn-hs/menu-type/${meal}/date/${year}/${month}/${day}`) + .then(res => res.json()) + .catch(() => []); + if (!menu_items) return; + return Object.fromEntries(menu_items.map((item: string) => [item, nutrition.get(item) ?? null])); + })) + return [date.toFormat('MM-dd'), { brunch, lunch }]; + } + + const days = Array + .from({ length: daysInMonth }, (_, i) => DateTime + .fromObject({ year, month, day: i + 1 }) + .setZone('America/Los_Angeles')) + .filter(day => { + const { periods } = getSchedule(day, alternates); + return periods && periods.filter(({ n }) => n === 'B' || n === 'L').length; + }); + + await Promise.all(Array + .from({ length: Math.ceil((days[days.length - 1].day - days[0].day) / 7 + 1) }, (_, i) => 7 * i + days[0].day) + .map(getNutrition)); + + const menu = Object.fromEntries(await Promise.all(days.map(getMenu))); + + await firestore.collection('gunn').doc('menu').set({ + timestamp: new Date().toISOString(), + menu: { ...current.menu, ...menu } + }); +}) \ No newline at end of file From 7fa3fc3c8e86bcf1b39540728b3e40ba7526b035 Mon Sep 17 00:00:00 2001 From: Leo Jeong <80350771+33tm@users.noreply.github.com> Date: Sat, 28 Sep 2024 21:23:01 -0700 Subject: [PATCH 08/10] refresh daily + account for empty menu --- functions/src/menu.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/functions/src/menu.ts b/functions/src/menu.ts index b17f37a7..33102d9b 100644 --- a/functions/src/menu.ts +++ b/functions/src/menu.ts @@ -21,7 +21,7 @@ export const menu = functions.https.onCall(async () => { } const current = (await firestore.collection('gunn').doc('menu').get()).data()!; - if (DateTime.fromISO(current.timestamp).plus({ week: 1 }) > now) + if (DateTime.fromISO(current.timestamp).plus({ day: 1 }) > now) return; const { daysInMonth, month, year } = now.plus({ week: 1 }); @@ -54,15 +54,18 @@ export const menu = functions.https.onCall(async () => { } async function getMenu(date: DateTime) { - const { year, month, day } = date + const { year, month, day } = date; const [brunch, lunch] = await Promise.all(['breakfast', 'lunch'].map(async meal => { const { menu_items } = await fetch(`${nutrislice}/digest/school/henry-m-gunn-hs/menu-type/${meal}/date/${year}/${month}/${day}`) .then(res => res.json()) .catch(() => []); if (!menu_items) return; return Object.fromEntries(menu_items.map((item: string) => [item, nutrition.get(item) ?? null])); - })) - return [date.toFormat('MM-dd'), { brunch, lunch }]; + })); + return [date.toFormat('MM-dd'), { + brunch: brunch ?? null, + lunch: lunch ?? null + }]; } const days = Array From a51ed0243255bff2556126a560961575e722be95 Mon Sep 17 00:00:00 2001 From: Leo Jeong <80350771+33tm@users.noreply.github.com> Date: Sat, 28 Sep 2024 21:34:06 -0700 Subject: [PATCH 09/10] menu integration --- .../components/layout/PeriodActionButton.tsx | 77 ++++++++++ client/src/components/lists/MenuModal.tsx | 53 +++++++ .../src/components/lists/NutritionModal.tsx | 142 ++++++++++++++++++ client/src/components/schedule/Period.tsx | 5 +- client/src/components/schedule/Periods.tsx | 9 +- client/src/contexts/MenuContext.ts | 52 +++++++ client/src/hooks/useMenu.ts | 42 ++++++ 7 files changed, 375 insertions(+), 5 deletions(-) create mode 100644 client/src/components/layout/PeriodActionButton.tsx create mode 100644 client/src/components/lists/MenuModal.tsx create mode 100644 client/src/components/lists/NutritionModal.tsx create mode 100644 client/src/contexts/MenuContext.ts create mode 100644 client/src/hooks/useMenu.ts diff --git a/client/src/components/layout/PeriodActionButton.tsx b/client/src/components/layout/PeriodActionButton.tsx new file mode 100644 index 00000000..d5b2d045 --- /dev/null +++ b/client/src/components/layout/PeriodActionButton.tsx @@ -0,0 +1,77 @@ +import { ReactNode, useContext, useState } from 'react'; +import { DateTime } from 'luxon'; +import MenuModal from '../lists/MenuModal'; +import MenuContext from '../../contexts/MenuContext'; + + +type ActionButtonProps = { + children: ReactNode, + now: boolean, + note?: string, + onClick?: () => void +} +function ActionButton(props: ActionButtonProps) { + const { children, now, note, onClick } = props; + return ( + + ) +} + +type PeriodActionButtonProps = { + date: DateTime, + name: string, + now: boolean, + note?: string +} +export default function PeriodActionButton(props: PeriodActionButtonProps) { + const { name } = props; + + if (name === 'PRIME' || name === 'Study Hall') + return + + if (name === 'Brunch' || name === 'Lunch') + return + + return <> +} + +function FlexiSCHEDAction(props: PeriodActionButtonProps) { + return ( + + + FlexiSCHED + + + ) +} + +function MenuAction(props: PeriodActionButtonProps) { + const { name, date } = props; + const { menu } = useContext(MenuContext); + const [modal, setModal] = useState(false); + + const formatted = date.toFormat('MM-dd'); + const meal = name.toLowerCase() as 'brunch' | 'lunch'; + + if (formatted in menu && menu[formatted][meal]) + return ( + <> + setModal(true)}> + Menu + + + + ) + + return <> +} \ No newline at end of file diff --git a/client/src/components/lists/MenuModal.tsx b/client/src/components/lists/MenuModal.tsx new file mode 100644 index 00000000..30a6b0bf --- /dev/null +++ b/client/src/components/lists/MenuModal.tsx @@ -0,0 +1,53 @@ +import { useState } from 'react'; +import { Dialog } from '@headlessui/react'; + +import NutritionModal from './NutritionModal'; +import CenteredModal from '../layout/CenteredModal'; +import { DangerOutlineButton } from '../layout/OutlineButton'; + +import type { Entry } from '../../contexts/MenuContext'; + + +type MenuModalProps = { + name: string, + items: { [item: string]: Entry }, + isOpen: boolean, + setIsOpen: (open: boolean) => void +} +export default function MenuModal(props: MenuModalProps) { + const { name, items, isOpen, setIsOpen } = props; + const [nutritionModal, setNutritionModal] = useState(null); + + return ( + + + {name} Menu + + +
+ {Object.entries(items).map(([item, nutrition]) => ( +
+
setNutritionModal(item)} + > + {item} +
+ setNutritionModal(null)} + /> +
+ ))} +
+ +
+ setIsOpen(false)}> + Close + +
+
+ ) +} diff --git a/client/src/components/lists/NutritionModal.tsx b/client/src/components/lists/NutritionModal.tsx new file mode 100644 index 00000000..1413ee79 --- /dev/null +++ b/client/src/components/lists/NutritionModal.tsx @@ -0,0 +1,142 @@ +import { ReactNode } from 'react'; +import { Dialog } from '@headlessui/react'; + +import CenteredModal from '../layout/CenteredModal'; +import { DangerOutlineButton } from '../layout/OutlineButton'; + +import type { Entry } from '../../contexts/MenuContext'; + + +type ItemProps = { + children: ReactNode, + value?: number, + dv?: number +} +function Item(props: ItemProps) { + const { children, value, dv } = props; + + if (value === null) + return <> + + return ( + <> +
+
+ {children} + {dv && {Math.floor((value! / dv) * 100)}%} +
+ + ) +} + +type NutritionModalProps = { + item: string, + nutrition: Entry, + isOpen: boolean, + setIsOpen: (open: boolean) => void +} +export default function NutritionModal(props: NutritionModalProps) { + const { + item, + nutrition: { + serving, + nutrition, + ingredients + }, + isOpen, + setIsOpen + } = props; + + return ( + + + {item} + + + {nutrition ? ( +
+ Nutrition Facts +
+ {serving && serving.serving_size_amount && serving.serving_size_unit && ( + <> +
+ Serving size + {serving.serving_size_amount} {serving.serving_size_unit} +
+
+ + )} + {nutrition.calories && ( + <> +
+ Calories + {nutrition.calories} +
+
+ + )} +
+ % Daily Value* +
+ + +

Total Fat {nutrition.g_fat}g

+
+ +

Saturated Fat {nutrition.g_saturated_fat}g

+
+ +

Trans Fat {nutrition.g_trans_fat}g

+
+ + +

Cholesterol {nutrition.mg_cholesterol}mg

+
+ + +

Sodium {nutrition.mg_sodium}mg

+
+ + +

Total Carbohydrate {nutrition.g_carbs}g

+
+ +

Dietary Fiber {nutrition.g_fiber}g

+
+ +

Total Sugars {nutrition.g_sugar}g

+
+ +

Includes {nutrition.g_added_sugar}g Added Sugars

+
+ + +

Protein {nutrition.g_protein}g

+
+ + +

+ * Percent Daily Values are based on a 2,000 calorie diet. +

+
+ + {ingredients && ( + <> +
+ Ingredients +

{ingredients}

+ + )} +
+ ) : ( +

No nutrition information available.

+ )} + +
+ setIsOpen(false)}> + Close + +
+
+ ) +} diff --git a/client/src/components/schedule/Period.tsx b/client/src/components/schedule/Period.tsx index 59575e10..36e7a524 100644 --- a/client/src/components/schedule/Period.tsx +++ b/client/src/components/schedule/Period.tsx @@ -5,6 +5,7 @@ import {DateTime} from 'luxon'; // Components import PillClubComponent from '../lists/PillClubComponent'; +import PeriodActionButton from '../layout/PeriodActionButton'; // Contexts import UserDataContext from '../../contexts/UserDataContext'; @@ -109,9 +110,7 @@ export default function Period(props: PeriodProps) { )} - + ); } diff --git a/client/src/components/schedule/Periods.tsx b/client/src/components/schedule/Periods.tsx index 84e0a1c2..dcf99cfc 100644 --- a/client/src/components/schedule/Periods.tsx +++ b/client/src/components/schedule/Periods.tsx @@ -9,9 +9,11 @@ import NoSchoolImage from './NoSchoolImage'; // Contexts import CurrentTimeContext from '../../contexts/CurrentTimeContext'; import UserDataContext, {SgyPeriodData, UserData} from '../../contexts/UserDataContext'; +import {MenuProvider} from '../../contexts/MenuContext'; // Utils import {useSchedule} from '../../hooks/useSchedule'; +import {useMenu} from '../../hooks/useMenu'; import {periodNameDefault} from '@watt/shared/util/schedule'; @@ -28,6 +30,9 @@ export default function Periods(props: PeriodsProps) { const format = userData.options.time === '24' ? 'H:mm' : 'h:mm a'; const classes = userData.classes as {[key: string]: SgyPeriodData}; + // Brunch/Lunch menu + const menu = useMenu(); + // HTML for a school day, assumes periods is populated const schoolDay = () => { // End time of the last period of the day @@ -43,7 +48,7 @@ export default function Periods(props: PeriodsProps) { const displayIndicator = periods && minutes < periods[periods.length - 1].e && minutes >= periods[0].s - 20; return ( - <> +

School ends at {end.toFormat(format)} today.

@@ -62,7 +67,7 @@ export default function Periods(props: PeriodsProps) { grades={grades} /> ))} - +
) } diff --git a/client/src/contexts/MenuContext.ts b/client/src/contexts/MenuContext.ts new file mode 100644 index 00000000..894e0d75 --- /dev/null +++ b/client/src/contexts/MenuContext.ts @@ -0,0 +1,52 @@ +import { createContext } from 'react'; + + +export type Entry = { + serving?: { + serving_size_amount: string, + serving_size_unit: string + }, + nutrition?: { + calories?: number, + g_fat?: number, + g_saturated_fat?: number, + g_trans_fat?: number, + mg_cholesterol?: number, + g_carbs?: number, + g_added_sugar?: number, + g_sugar?: number, + mg_potassium?: number, + mg_sodium?: number, + g_fiber?: number, + g_protein?: number, + mg_iron?: number, + mg_calcium?: number, + mg_vitamin_c?: number, + iu_vitamin_a?: number, + re_vitamin_a?: number, + mcg_vitamin_a?: number, + mg_vitamin_d?: number, + mcg_vitamin_d?: number, + }, + ingredients?: string +} + +export type Menu = { + timestamp: string, + menu: { + [date: string]: { + brunch: { [item: string]: Entry }, + lunch: { [item: string]: Entry } + } + } +} + +export const defaultMenu: Menu = { + timestamp: new Date().toISOString(), + menu: {} +} + +const MenuContext = createContext(defaultMenu); + +export const MenuProvider = MenuContext.Provider; +export default MenuContext; \ No newline at end of file diff --git a/client/src/hooks/useMenu.ts b/client/src/hooks/useMenu.ts new file mode 100644 index 00000000..53d6150f --- /dev/null +++ b/client/src/hooks/useMenu.ts @@ -0,0 +1,42 @@ +import { useEffect, useState } from 'react'; +import { defaultMenu, Menu } from '../contexts/MenuContext'; +import { DateTime } from 'luxon'; + +import { doc } from 'firebase/firestore'; +import { httpsCallable } from 'firebase/functions'; +import { useFirestore, useFirestoreDoc, useFunctions } from 'reactfire'; + + +export function useMenu() { + const firestore = useFirestore(); + const functions = useFunctions(); + + const localStorageRaw = localStorage.getItem('menu'); + const [menu, setMenu] = useState(tryParseLocalStorageMenu()); + const { status, data: firebaseDoc } = useFirestoreDoc(doc(firestore, 'gunn/menu')); + + useEffect(() => { + const parsed = tryParseLocalStorageMenu(); + setMenu(parsed); + // Regenerate daily + if (DateTime.fromISO(parsed.timestamp).plus({ day: 1 }) < DateTime.now()) + httpsCallable(functions, 'menu')(); + localStorage.setItem('menu', JSON.stringify(parsed)); + }, [localStorageRaw]); + + useEffect(() => { + if (status !== 'success' || !firebaseDoc.exists()) return; + localStorage.setItem('menu', JSON.stringify(firebaseDoc.data())); + }, [firebaseDoc]); + + function tryParseLocalStorageMenu() { + if (!localStorageRaw) return defaultMenu; + try { + return JSON.parse(localStorageRaw) as Menu; + } catch { + return defaultMenu + } + } + + return menu; +} \ No newline at end of file From 11d0b8f79c5ca507e125d6b15b0f1858f009bc65 Mon Sep 17 00:00:00 2001 From: Leo Jeong <80350771+33tm@users.noreply.github.com> Date: Sat, 28 Sep 2024 22:25:46 -0700 Subject: [PATCH 10/10] use node-fetch + ignore items without nutrition info --- client/src/components/lists/MenuModal.tsx | 16 +- .../src/components/lists/NutritionModal.tsx | 158 +++++++++--------- functions/src/menu.ts | 1 + 3 files changed, 90 insertions(+), 85 deletions(-) diff --git a/client/src/components/lists/MenuModal.tsx b/client/src/components/lists/MenuModal.tsx index 30a6b0bf..0b4d4fc3 100644 --- a/client/src/components/lists/MenuModal.tsx +++ b/client/src/components/lists/MenuModal.tsx @@ -29,16 +29,18 @@ export default function MenuModal(props: MenuModalProps) {
setNutritionModal(item)} + onClick={() => nutrition && setNutritionModal(item)} > {item}
- setNutritionModal(null)} - /> + {nutrition && ( + setNutritionModal(null)} + /> + )}
))} diff --git a/client/src/components/lists/NutritionModal.tsx b/client/src/components/lists/NutritionModal.tsx index 1413ee79..27bebe0e 100644 --- a/client/src/components/lists/NutritionModal.tsx +++ b/client/src/components/lists/NutritionModal.tsx @@ -53,84 +53,86 @@ export default function NutritionModal(props: NutritionModalProps) { {item} - {nutrition ? ( -
- Nutrition Facts -
- {serving && serving.serving_size_amount && serving.serving_size_unit && ( - <> -
- Serving size - {serving.serving_size_amount} {serving.serving_size_unit} -
-
- - )} - {nutrition.calories && ( - <> -
- Calories - {nutrition.calories} -
-
- - )} -
- % Daily Value* -
- - -

Total Fat {nutrition.g_fat}g

-
- -

Saturated Fat {nutrition.g_saturated_fat}g

-
- -

Trans Fat {nutrition.g_trans_fat}g

-
- - -

Cholesterol {nutrition.mg_cholesterol}mg

-
- - -

Sodium {nutrition.mg_sodium}mg

-
- - -

Total Carbohydrate {nutrition.g_carbs}g

-
- -

Dietary Fiber {nutrition.g_fiber}g

-
- -

Total Sugars {nutrition.g_sugar}g

-
- -

Includes {nutrition.g_added_sugar}g Added Sugars

-
- - -

Protein {nutrition.g_protein}g

-
- - -

- * Percent Daily Values are based on a 2,000 calorie diet. -

-
- - {ingredients && ( - <> -
- Ingredients -

{ingredients}

- - )} -
- ) : ( -

No nutrition information available.

- )} +
+ {nutrition ? ( + <> + Nutrition Facts +
+ {serving && serving.serving_size_amount && serving.serving_size_unit && ( + <> +
+ Serving size + {serving.serving_size_amount} {serving.serving_size_unit} +
+
+ + )} + {nutrition.calories && ( + <> +
+ Calories + {nutrition.calories} +
+
+ + )} +
+ % Daily Value* +
+ + +

Total Fat {nutrition.g_fat}g

+
+ +

Saturated Fat {nutrition.g_saturated_fat}g

+
+ +

Trans Fat {nutrition.g_trans_fat}g

+
+ + +

Cholesterol {nutrition.mg_cholesterol}mg

+
+ + +

Sodium {nutrition.mg_sodium}mg

+
+ + +

Total Carbohydrate {nutrition.g_carbs}g

+
+ +

Dietary Fiber {nutrition.g_fiber}g

+
+ +

Total Sugars {nutrition.g_sugar}g

+
+ +

Includes {nutrition.g_added_sugar}g Added Sugars

+
+ + +

Protein {nutrition.g_protein}g

+
+ + +

+ * Percent Daily Values are based on a 2,000 calorie diet. +

+
+ + ) : ( +

No nutrition information available.

+ )} + + {ingredients && ( + <> +
+ Ingredients +

{ingredients}

+ + )} +
setIsOpen(false)}> diff --git a/functions/src/menu.ts b/functions/src/menu.ts index 33102d9b..d9a80586 100644 --- a/functions/src/menu.ts +++ b/functions/src/menu.ts @@ -1,5 +1,6 @@ import * as functions from 'firebase-functions'; import admin from './util/adminInit'; +import fetch from 'node-fetch'; import { DateTime } from 'luxon'; import { getAlternates } from './util/apiUtil';