diff --git a/app/api/menu_service.tsx b/app/api/menu_service.tsx index 63452c7..70b1737 100644 --- a/app/api/menu_service.tsx +++ b/app/api/menu_service.tsx @@ -1,7 +1,6 @@ import { useQuery, useMutation } from '@tanstack/react-query'; import { rootQueryUrl, menusEndpoint } from '~/root'; import type { DailyMenu, DailyMenuDTO } from '~/interfaces'; -import { TAG_ID_MAP } from '~/interfaces'; import { getAccessToken } from '~/api/user_service'; import i18n from '~/i18n'; @@ -77,23 +76,8 @@ function upsertLocalDraft( return stored; } -function convertTagsToObjects(menu: any) { - return { - ...menu, - dishes: (menu.dishes || []).map((dish: any) => ({ - ...dish, - tags: (dish.tags || []) - .map((tag: string) => ({ - id: TAG_ID_MAP[tag as keyof typeof TAG_ID_MAP], - value: tag, - })) - .filter((t: any) => t.id), - })), - }; -} - function getUiLanguageCode() { - return (i18n.language || 'en').split('-')[0].toUpperCase(); + return (i18n.language || 'en').split('-')[0].toLowerCase(); } function mapLocalizedDishes(rawDishes: any[]): DailyMenu['dishes'] { @@ -106,7 +90,7 @@ function mapLocalizedDishes(rawDishes: any[]): DailyMenu['dishes'] { image: dishData?.image || '', category: dishData?.category || 'MAIN_COURSE', price: String(item?.price ?? dishData?.price ?? '0'), - tags: Array.isArray(dishData?.tags) ? dishData.tags : [], + allergens: Array.isArray(dishData?.allergens) ? dishData.allergens : [], } as any; }); } @@ -196,35 +180,19 @@ export const useUpdateDailyMenuWithToken = (token: string) => } const currentLanguage = getUiLanguageCode(); - const body = JSON.stringify(convertTagsToObjects(menu)); - console.log('[updateMenu] sending body:', body); - // Try POST first (create), fall back to PUT (update) if 405 - let response = await fetch(`${rootQueryUrl}/${menusEndpoint}/${restaurantId}`, { - method: 'POST', + const response = await fetch(`${rootQueryUrl}/${menusEndpoint}/${restaurantId}`, { + method: 'PUT', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, 'Accept-Language': currentLanguage, }, - body, + body: JSON.stringify(menu), }); - if (response.status === 405) { - response = await fetch(`${rootQueryUrl}/${menusEndpoint}/${restaurantId}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}`, - 'Accept-Language': currentLanguage, - }, - body, - }); - } - if (!response.ok) { const text = await response.text().catch(() => ''); - console.log('[updateMenu] error response:', text); throw new Error(`Failed to update menu (${response.status}): ${text}`); } @@ -403,7 +371,7 @@ export const menuService = { name: dish.name, category: dish.category ?? 'MAIN_COURSE', price: typeof dish.price === 'number' ? dish.price : Number(dish.price ?? 0), - tags: (dish as any).tags?.map((t: any) => t.value ?? t) ?? [], + allergens: dish.allergens ?? [], })), }; } catch { @@ -475,7 +443,7 @@ export const menuService = { 'Content-Type': 'application/json', 'Accept-Language': currentLanguage, }, - body: JSON.stringify(convertTagsToObjects(menu)), + body: JSON.stringify(menu), }); if (!response.ok) { diff --git a/app/api/restaurant_service.tsx b/app/api/restaurant_service.tsx index f7e4b14..c2a7750 100644 --- a/app/api/restaurant_service.tsx +++ b/app/api/restaurant_service.tsx @@ -1,29 +1,10 @@ -import { useQuery } from '@tanstack/react-query'; import { rootQueryUrl, allRestaurantsEndpoint } from '~/root'; import { apiGet, useApiPut } from '~/api/api'; -import type { - Restaurant, - RankedRestaurant, - RestaurantDetailsDTO, - UpdateRestaurantRequest, -} from '~/interfaces'; +import type { Restaurant, RestaurantDetailsDTO, UpdateRestaurantRequest } from '~/interfaces'; export let useGetAllRestaurants = () => apiGet('restaurants', `${rootQueryUrl}/${allRestaurantsEndpoint}`); -export let useGetRestaurantRecommendations = (token: string | undefined) => - useQuery({ - queryKey: ['restaurantRecommendations', token], - queryFn: async () => { - const response = await fetch(`${rootQueryUrl}/${allRestaurantsEndpoint}/recommendation`, { - headers: { Authorization: `Bearer ${token}` }, - }); - if (!response.ok) throw new Error(`API Error: ${response.status}`); - return response.json() as Promise; - }, - enabled: !!token, - }); - export let useGetRestaurantDetails = (id: string) => apiGet( `restaurant-${id}`, diff --git a/app/api/user_service.tsx b/app/api/user_service.tsx index edb2285..57f8cfa 100644 --- a/app/api/user_service.tsx +++ b/app/api/user_service.tsx @@ -1,6 +1,5 @@ import { useQuery } from '@tanstack/react-query'; import { rootQueryUrl } from '~/root'; -import type { TagValue, TagType } from '~/interfaces'; export interface RestaurantListDTO { id: string; @@ -105,42 +104,3 @@ export const useGetFirstOwnedRestaurant = (token: string | undefined) => { enabled: isBrowser && !!token, }); }; - -export type PreferenceType = 'INCLUDE' | 'EXCLUDE'; - -export interface UserPreferenceDTO { - tagValue: TagValue; - tagType: TagType; - preferenceType: PreferenceType; -} - -export const useGetPreferences = (token: string | undefined) => - useQuery({ - queryKey: ['userPreferences'], - queryFn: async () => { - const response = await fetch(`${rootQueryUrl}/api/users`, { - headers: { Authorization: `Bearer ${token}` }, - }); - if (!response.ok) throw new Error('Failed to fetch preferences'); - return response.json(); - }, - enabled: !!token, - }); - -export async function savePreferences( - token: string, - preferences: Array<{ tagValue: TagValue; preferenceType: PreferenceType }> -): Promise { - const response = await fetch(`${rootQueryUrl}/api/users/preferences`, { - method: 'POST', - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ preferences }), - }); - if (!response.ok) { - const text = await response.text().catch(() => ''); - throw new Error(`Failed to save preferences (${response.status}): ${text}`); - } -} diff --git a/app/components/menu/dish/dish_info_box.tsx b/app/components/menu/dish/dish_info_box.tsx index d684901..8469a30 100644 --- a/app/components/menu/dish/dish_info_box.tsx +++ b/app/components/menu/dish/dish_info_box.tsx @@ -1,17 +1,23 @@ import type { Dish } from '~/interfaces'; -import { Milk, Nut, Wheat, Leaf, Vegan } from 'lucide-react'; +import { Beef, Milk, Nut, Wheat, CirclePile } from 'lucide-react'; import type { ComponentType } from 'react'; -const TAG_ICON_MAP: Record; label: string }> = { - GLUTEN: { icon: Wheat, label: 'Gluten' }, - LACTOSE: { icon: Milk, label: 'Lactose' }, - NUTS: { icon: Nut, label: 'Nuts' }, - VEGAN: { icon: Vegan, label: 'Vegan' }, - VEGETARIAN: { icon: Leaf, label: 'Vegetarian' }, -}; +const ALLERGEN_ICON_MAP: Record; label: string }> = + { + GLUTEN: { icon: Wheat, label: 'Gluten' }, + LACTOSE: { icon: Milk, label: 'Lactose' }, + MEAT: { icon: Beef, label: 'Meat' }, + NUTS: { icon: Nut, label: 'Nuts' }, + SESAME: { icon: CirclePile, label: 'Sesame' }, + }; export function DishInfo({ dish }: { dish: Dish }) { - const tagIcons = (dish.tags ?? []).map(tag => TAG_ICON_MAP[tag.value]).filter(Boolean); + const allergenIcons = (dish.allergens ?? []) + .map(allergen => { + const key = allergen.toUpperCase(); + return ALLERGEN_ICON_MAP[key]; + }) + .filter(Boolean); return (
@@ -22,8 +28,8 @@ export function DishInfo({ dish }: { dish: Dish }) {
- {tagIcons.length > 0 ? ( - tagIcons.map(({ icon: Icon, label }) => ( + {allergenIcons.length > 0 ? ( + allergenIcons.map(({ icon: Icon, label }) => ( }, ]; - const dishFilterStrings = filters as string[]; + // map UI filter values to the dish_filters expected strings + const filterValueMap: Record = { + vegan: 'vegan', + vegetarian: 'vegetarian', + lactoseFree: 'lactose-free', + glutenFree: 'gluten-free', + }; + + const dishFilterStrings = filters.map(f => filterValueMap[f]); const filteredByAllergens: Dish[] = filterDishes(dishes, dishFilterStrings); const normalizedQuery = query.trim().toLowerCase(); const filteredDishes = filteredByAllergens.filter(dish => { @@ -95,26 +103,29 @@ export function DishListComponent({ className="!bg-white dark:!bg-black border-gray-300 dark:border-zinc-700 shadow-sm" /> -
-
- {filterOptions.map(option => { - const active = filters.includes(option.value); - return ( - - ); - })} +
+
+
+ {filterOptions.map(option => { + const active = filters.includes(option.value); + + return ( + + ); + })} +
diff --git a/app/components/menu/menu_translations.ts b/app/components/menu/menu_translations.ts index f90442a..f925a43 100644 --- a/app/components/menu/menu_translations.ts +++ b/app/components/menu/menu_translations.ts @@ -3,14 +3,9 @@ type TranslateFn = (key: string) => string; const ALLERGEN_KEY_MAP: Record = { GLUTEN: 'menuForm.allergenGluten', LACTOSE: 'menuForm.allergenLactose', + MEAT: 'menuForm.allergenMeat', NUTS: 'menuForm.allergenNuts', SESAME: 'menuForm.allergenSesame', - VEGAN: 'filters.vegan', - VEGETARIAN: 'filters.vegetarian', - ITALIAN: 'filters.italian', - POLISH: 'filters.polish', - ASIAN: 'filters.asian', - FAST_FOOD: 'filters.fastFood', }; const CATEGORY_KEY_MAP: Record = { diff --git a/app/components/overview/filter_bar.tsx b/app/components/overview/filter_bar.tsx index 9d48dc4..0f6b5ba 100644 --- a/app/components/overview/filter_bar.tsx +++ b/app/components/overview/filter_bar.tsx @@ -7,17 +7,7 @@ import { useTranslation } from 'react-i18next'; import { ToggleGroup, ToggleGroupItem } from '~/shadcn/components/ui/toggle-group'; -export type FilterValue = - | 'vegan' - | 'vegetarian' - | 'lactoseFree' - | 'glutenFree' - | 'nutsFree' - | 'hasMenuToday' - | 'italian' - | 'polish' - | 'asian' - | 'fastFood'; +export type FilterValue = 'vegan' | 'vegetarian' | 'lactoseFree' | 'glutenFree' | 'hasMenuToday'; export interface FilterBarProps { value: FilterValue[]; diff --git a/app/components/overview/overview_component.tsx b/app/components/overview/overview_component.tsx index 8f73e53..11bd17d 100644 --- a/app/components/overview/overview_component.tsx +++ b/app/components/overview/overview_component.tsx @@ -16,9 +16,8 @@ import { BottomNav } from '~/components/shared/bottom_nav'; import { SearchBar } from '~/components/overview/search_bar'; import { type FilterValue } from '~/components/overview/filter_bar'; import { useRestaurantItemsWithFilters } from '~/components/overview/service_section/use_restaurant_items_with_filters'; -import { useGetAllRestaurants, useGetRestaurantRecommendations } from '~/api/restaurant_service'; +import { useGetAllRestaurants } from '~/api/restaurant_service'; import { useGetAllVendingMachines } from '~/api/vending_machine_service'; -import { useKeycloak } from '@react-keycloak/web'; import { appRoutes } from '~/lib/app_routes'; import { useRestaurantStore } from '~/store/restaurant_store'; import type { Facility, Restaurant, VendingMachine } from '~/interfaces'; @@ -138,16 +137,12 @@ function VendingMachineCard({ export function OverviewComponent() { const { t } = useTranslation(); const navigate = useNavigate(); - const { keycloak, initialized } = useKeycloak(); const setSelectedRestaurant = useRestaurantStore(state => state.setSelectedRestaurant); const [mode, setMode] = React.useState<'restaurants' | 'vending'>('restaurants'); const [filters, setFilters] = React.useState([]); const [query, setQuery] = React.useState(''); const [selectedPoint, setSelectedPoint] = React.useState(null); - const token = initialized && keycloak.authenticated ? keycloak.token : undefined; - const { data: recommendationsData } = useGetRestaurantRecommendations(token); - const { isPending, error, items } = useRestaurantItemsWithFilters({ carouselItemSource: useGetAllRestaurants, filters, @@ -173,18 +168,8 @@ export function OverviewComponent() { return name.includes(normalizedQuery) || description.includes(normalizedQuery); }); - const scoreMap = React.useMemo(() => { - if (!recommendationsData) return {} as Record; - return Object.fromEntries(recommendationsData.map(r => [r.restaurant.id, r.score])); - }, [recommendationsData]); - - const sortedFilteredItems = React.useMemo(() => { - if (!recommendationsData) return filteredItems; - return [...filteredItems].sort((a, b) => (scoreMap[b.id] ?? 0) - (scoreMap[a.id] ?? 0)); - }, [filteredItems, recommendationsData, scoreMap]); - - const openItems = sortedFilteredItems.filter(item => item.openNow); - const closedItems = sortedFilteredItems.filter(item => !item.openNow); + const openItems = filteredItems.filter(item => item.openNow); + const closedItems = filteredItems.filter(item => !item.openNow); const filteredVendingMachines = (vendingMachines ?? []).filter(vendingMachine => { if (!normalizedQuery) return true; @@ -305,26 +290,29 @@ export function OverviewComponent() { /> {mode === 'restaurants' && ( -
-
- {filterOptions.map(option => { - const active = filters.includes(option.value); - return ( - - ); - })} +
+
+
+ {filterOptions.map(option => { + const active = filters.includes(option.value); + + return ( + + ); + })} +
)} diff --git a/app/components/overview/service_section/use_restaurant_items_with_filters.ts b/app/components/overview/service_section/use_restaurant_items_with_filters.ts index a0f91f4..5a8b2c3 100644 --- a/app/components/overview/service_section/use_restaurant_items_with_filters.ts +++ b/app/components/overview/service_section/use_restaurant_items_with_filters.ts @@ -15,8 +15,6 @@ import { hasVegetarianOption, hasLactoseFreeOption, hasGlutenFreeOption, - hasNutsFreeOption, - hasCuisineOption, } from '~/shadcn/lib/dish_filters'; interface UseRestaurantItemsWithFiltersProps { @@ -84,16 +82,6 @@ export function useRestaurantItemsWithFilters({ return hasLactoseFreeOption(menu); case 'glutenFree': return hasGlutenFreeOption(menu); - case 'nutsFree': - return hasNutsFreeOption(menu); - case 'italian': - return hasCuisineOption(menu, 'ITALIAN'); - case 'polish': - return hasCuisineOption(menu, 'POLISH'); - case 'asian': - return hasCuisineOption(menu, 'ASIAN'); - case 'fastFood': - return hasCuisineOption(menu, 'FAST_FOOD'); case 'hasMenuToday': return !!menu && !!menu.dishes && menu.dishes.length > 0; default: @@ -103,5 +91,5 @@ export function useRestaurantItemsWithFilters({ }); } - return { isPending, error, items: filtered, menusData: menusData ?? {} }; + return { isPending, error, items: filtered }; } diff --git a/app/components/profile/profile_component.tsx b/app/components/profile/profile_component.tsx index 0d14171..9c3e1a8 100644 --- a/app/components/profile/profile_component.tsx +++ b/app/components/profile/profile_component.tsx @@ -1,178 +1,9 @@ -import React from 'react'; import { TopBar } from '~/components/shared/top_bar'; import { BottomNav } from '~/components/shared/bottom_nav'; import { useKeycloak } from '@react-keycloak/web'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { appRoutes } from '~/lib/app_routes'; -import { useGetPreferences, useGetCurrentUserWithToken, savePreferences } from '~/api/user_service'; -import type { TagValue } from '~/interfaces'; - -const ALL_TAGS: TagValue[] = [ - 'ITALIAN', - 'POLISH', - 'ASIAN', - 'FAST_FOOD', - 'VEGAN', - 'VEGETARIAN', - 'GLUTEN', - 'LACTOSE', - 'NUTS', - 'SESAME', -]; - -function PreferencesSection({ - token, - userReady, - t, -}: { - token: string | undefined; - userReady: boolean; - t: (key: string) => string; -}) { - const { data: prefsData, isLoading } = useGetPreferences(userReady ? token : undefined); - const [include, setInclude] = React.useState([]); - const [exclude, setExclude] = React.useState([]); - const [saving, setSaving] = React.useState(false); - const [saved, setSaved] = React.useState(false); - const [error, setError] = React.useState(null); - - React.useEffect(() => { - if (!prefsData) return; - setInclude(prefsData.filter(p => p.preferenceType === 'INCLUDE').map(p => p.tagValue)); - setExclude(prefsData.filter(p => p.preferenceType === 'EXCLUDE').map(p => p.tagValue)); - }, [prefsData]); - - const toggleInclude = (value: TagValue) => { - setInclude(prev => (prev.includes(value) ? prev.filter(v => v !== value) : [...prev, value])); - setExclude(prev => prev.filter(v => v !== value)); - setSaved(false); - }; - - const toggleExclude = (value: TagValue) => { - setExclude(prev => (prev.includes(value) ? prev.filter(v => v !== value) : [...prev, value])); - setInclude(prev => prev.filter(v => v !== value)); - setSaved(false); - }; - - const handleSave = async () => { - if (!token) return; - setSaving(true); - setError(null); - try { - // Ensure user exists in backend DB before saving preferences - const meRes = await fetch('/api/users/me', { headers: { Authorization: `Bearer ${token}` } }); - const meText = await meRes.text().catch(() => ''); - console.log('[preferences] /api/users/me ->', meRes.status, meText.slice(0, 200)); - if (!meRes.ok) { - throw new Error(`/api/users/me failed (${meRes.status}): ${meText}`); - } - await savePreferences(token, [ - ...include.map(tagValue => ({ tagValue, preferenceType: 'INCLUDE' as const })), - ...exclude.map(tagValue => ({ tagValue, preferenceType: 'EXCLUDE' as const })), - ]); - setSaved(true); - setTimeout(() => setSaved(false), 3000); - } catch (err) { - setError(err instanceof Error ? err.message : t('profile.failedToSavePreferences')); - } finally { - setSaving(false); - } - }; - - const tagLabel = (tag: TagValue): string => { - const map: Partial> = { - VEGAN: t('filters.vegan'), - VEGETARIAN: t('filters.vegetarian'), - GLUTEN: t('menuForm.allergenGluten'), - LACTOSE: t('menuForm.allergenLactose'), - NUTS: t('menuForm.allergenNuts'), - SESAME: t('menuForm.allergenSesame'), - ITALIAN: t('profile.tagItalian'), - POLISH: t('profile.tagPolish'), - ASIAN: t('profile.tagAsian'), - FAST_FOOD: t('profile.tagFastFood'), - }; - return map[tag] ?? tag; - }; - - if (isLoading) { - return ( -

{t('profile.loadingPreferences')}

- ); - } - - return ( -
-
-

- {t('profile.preferences')} -

-

- {t('profile.preferencesSubtitle')} -

-
- -
-

- {t('profile.iPrefer')} -

-
- {ALL_TAGS.map(tag => ( - - ))} -
-
- -
-

- {t('profile.iAvoid')} -

-
- {ALL_TAGS.map(tag => ( - - ))} -
-
- - {error &&

{error}

} - - -
- ); -} export default function ProfileComponent() { const { keycloak, initialized } = useKeycloak(); @@ -199,7 +30,6 @@ export default function ProfileComponent() { const token = keycloak.tokenParsed; const roles = token?.realm_access?.roles || []; const isOwner = roles.includes('restaurant_owner'); - const { data: backendUser } = useGetCurrentUserWithToken(keycloak.token); if (!token) { return ( @@ -301,17 +131,6 @@ export default function ProfileComponent() { {t('profile.openManagerPanel')} )} - - {!isOwner && ( - <> -
- - - )}
diff --git a/app/components/staff/menu/common/dish_editor.tsx b/app/components/staff/menu/common/dish_editor.tsx index 4f6da95..65323d3 100644 --- a/app/components/staff/menu/common/dish_editor.tsx +++ b/app/components/staff/menu/common/dish_editor.tsx @@ -1,25 +1,25 @@ import * as React from 'react'; -import { - MENU_DIETARY_TAGS, - MENU_ALLERGEN_TAGS, - MENU_CUISINE_TAGS, - type Dish, - type TagValue, -} from '~/interfaces'; +import { MENU_ALLERGENS, type Dish, type Allergen } from '~/interfaces'; import { useTranslation } from 'react-i18next'; import { translateAllergen, translateCategory } from '~/components/menu/menu_translations'; const CATEGORY_OPTIONS = ['SOUP', 'MAIN_COURSE']; interface DishEditorProps { - dish: Dish & { category?: string; tags?: string[] | any[] }; + dish: Dish & { category?: string }; index: number; onDishChange: (index: number, field: keyof Dish | 'category', value: any) => void; - onTagToggle: (index: number, tag: TagValue) => void; + onAllergenToggle: (index: number, allergen: Allergen) => void; onRemove: (index: number) => void; } -export function DishEditor({ dish, index, onDishChange, onTagToggle, onRemove }: DishEditorProps) { +export function DishEditor({ + dish, + index, + onDishChange, + onAllergenToggle, + onRemove, +}: DishEditorProps) { const { t } = useTranslation(); return ( @@ -82,66 +82,24 @@ export function DishEditor({ dish, index, onDishChange, onTagToggle, onRemove }: />
-
-
-

- {t('menuForm.dietaryTags')} -

-
- {MENU_DIETARY_TAGS.map(tag => { - const selected = (dish.tags || []).includes(tag); - return ( - - ); - })} -
-
-
-

- {t('menuForm.allergenTags')} -

-
- {MENU_ALLERGEN_TAGS.map(tag => { - const selected = (dish.tags || []).includes(tag); - return ( - - ); - })} -
-
-
-

- {t('menuForm.cuisine')} -

-
- {MENU_CUISINE_TAGS.map(tag => { - const selected = (dish.tags || []).includes(tag); - return ( - - ); - })} -
+
+ +
+ {MENU_ALLERGENS.map(allergen => { + const selected = (dish.allergens || []).includes(allergen); + return ( + + ); + })}
diff --git a/app/components/staff/menu/dish_editor.tsx b/app/components/staff/menu/dish_editor.tsx index 5f062a4..76fd8c9 100644 --- a/app/components/staff/menu/dish_editor.tsx +++ b/app/components/staff/menu/dish_editor.tsx @@ -1,20 +1,25 @@ import * as React from 'react'; -import type { Dish, TagValue } from '~/interfaces'; -import { MENU_DIETARY_TAGS, MENU_ALLERGEN_TAGS, MENU_CUISINE_TAGS } from '~/interfaces'; +import type { Dish, Allergen } from '~/interfaces'; import { useTranslation } from 'react-i18next'; -import { translateAllergen } from '~/components/menu/menu_translations'; +const ALLERGEN_OPTIONS: Allergen[] = ['GLUTEN', 'LACTOSE', 'MEAT', 'NUTS']; const CATEGORY_OPTIONS = ['SOUP', 'MAIN_COURSE']; interface DishEditorProps { dish: Dish; index: number; onDishChange: (index: number, field: keyof Dish, value: any) => void; - onTagToggle: (index: number, tag: TagValue) => void; + onAllergenToggle: (index: number, allergen: Allergen) => void; onRemove: (index: number) => void; } -export function DishEditor({ dish, index, onDishChange, onTagToggle, onRemove }: DishEditorProps) { +export function DishEditor({ + dish, + index, + onDishChange, + onAllergenToggle, + onRemove, +}: DishEditorProps) { const { t } = useTranslation(); return ( @@ -77,66 +82,24 @@ export function DishEditor({ dish, index, onDishChange, onTagToggle, onRemove }: />
-
-
-

- {t('menuForm.dietaryTags')} -

-
- {MENU_DIETARY_TAGS.map(tag => { - const selected = ((dish as any).tags || []).includes(tag); - return ( - - ); - })} -
-
-
-

- {t('menuForm.allergenTags')} -

-
- {MENU_ALLERGEN_TAGS.map(tag => { - const selected = ((dish as any).tags || []).includes(tag); - return ( - - ); - })} -
-
-
-

- {t('menuForm.cuisine')} -

-
- {MENU_CUISINE_TAGS.map(tag => { - const selected = ((dish as any).tags || []).includes(tag); - return ( - - ); - })} -
+
+ +
+ {ALLERGEN_OPTIONS.map(allergen => { + const selected = (dish.allergens || []).includes(allergen); + return ( + + ); + })}
diff --git a/app/components/staff/menu/manual_menu/menu_draft.tsx b/app/components/staff/menu/manual_menu/menu_draft.tsx index 0df24db..a17ec42 100644 --- a/app/components/staff/menu/manual_menu/menu_draft.tsx +++ b/app/components/staff/menu/manual_menu/menu_draft.tsx @@ -2,7 +2,7 @@ import React, { useEffect } from 'react'; import { useKeycloak } from '@react-keycloak/web'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { menuService, type StoredMenuDraft } from '~/api/menu_service'; -import type { DailyMenu, DailyMenuDTO, Dish, TagValue } from '~/interfaces'; +import type { DailyMenu, DailyMenuDTO, Dish, Allergen } from '~/interfaces'; import { LoadingSpinner } from '~/components/staff/common/loading_spinner'; import { ErrorState, EmptyState } from '~/components/staff/common/error_state'; import { StatusBanner } from '~/components/staff/common/status_banner'; @@ -130,15 +130,17 @@ export function StaffMenuDraft({ restaurantId }: StaffMenuDraftProps) { }); }; - const handleTagToggle = (dishIndex: number, tag: TagValue) => { + const handleAllergenToggle = (dishIndex: number, allergen: Allergen) => { if (!menu || !menu.dishes) return; - const dish = menu.dishes[dishIndex] as any; - const tags = dish.tags || []; + const dish = menu.dishes[dishIndex]; + const allergens = dish.allergens || []; - const updatedTags = tags.includes(tag) ? tags.filter((t: string) => t !== tag) : [...tags, tag]; + const updatedAllergens = allergens.includes(allergen) + ? allergens.filter(a => a !== allergen) + : [...allergens, allergen]; - handleDishChange(dishIndex, 'tags' as any, updatedTags); + handleDishChange(dishIndex, 'allergens', updatedAllergens); }; const handleRemoveDish = (index: number) => { @@ -185,7 +187,7 @@ export function StaffMenuDraft({ restaurantId }: StaffMenuDraftProps) { name: dish.name, category: dish.category ?? 'MAIN_COURSE', price: normalizedPrice, - tags: (dish as any).tags ?? [], + allergens: dish.allergens ?? [], }; if (typeof dish.id === 'string' && isUuid(dish.id)) { @@ -220,7 +222,7 @@ export function StaffMenuDraft({ restaurantId }: StaffMenuDraftProps) { name: dish.name, category: dish.category ?? 'MAIN_COURSE', price: typeof dish.price === 'number' ? dish.price : Number(dish.price ?? 0), - tags: dish.tags ?? [], + allergens: dish.allergens ?? [], })), }; @@ -368,7 +370,7 @@ export function StaffMenuDraft({ restaurantId }: StaffMenuDraftProps) { dish={dish} index={index} onDishChange={handleDishChange} - onTagToggle={handleTagToggle} + onAllergenToggle={handleAllergenToggle} onRemove={handleRemoveDish} /> ))} diff --git a/app/components/staff/menu/manual_menu/menu_form.tsx b/app/components/staff/menu/manual_menu/menu_form.tsx index ccaae50..8071f79 100644 --- a/app/components/staff/menu/manual_menu/menu_form.tsx +++ b/app/components/staff/menu/manual_menu/menu_form.tsx @@ -14,12 +14,7 @@ import { import { Checkbox } from '~/shadcn/components/ui/checkbox'; import { Plus, Trash } from 'lucide-react'; import { Alert, AlertTitle, AlertDescription } from '~/shadcn/components/ui/alert'; -import { - MENU_DIETARY_TAGS, - MENU_ALLERGEN_TAGS, - MENU_CUISINE_TAGS, - type DishDTO, -} from '~/interfaces'; +import { MENU_ALLERGENS, type DishDTO } from '~/interfaces'; import { menuService, useSaveMenuDraftWithToken, @@ -87,7 +82,7 @@ export function DailyMenuForm({ }, [searchParams, restaurantId]); const addDish = () => { - setDishes([...dishes, { name: '', category: '', price: 0, tags: [] }]); + setDishes([...dishes, { name: '', category: '', price: 0, allergens: [] }]); }; const updateDish = (index: number, field: keyof DishDTO, value: any) => { @@ -96,12 +91,12 @@ export function DailyMenuForm({ setDishes(updated); }; - const toggleTag = (index: number, tag: string) => { + const toggleAllergen = (index: number, allergen: string) => { const updated = [...dishes]; - const exists = updated[index].tags.includes(tag); - updated[index].tags = exists - ? updated[index].tags.filter(a => a !== tag) - : [...updated[index].tags, tag]; + const exists = updated[index].allergens.includes(allergen); + updated[index].allergens = exists + ? updated[index].allergens.filter(a => a !== allergen) + : [...updated[index].allergens, allergen]; setDishes(updated); }; @@ -349,69 +344,25 @@ export function DailyMenuForm({ />
-
-
-

- {t('menuForm.dietaryTags')} -

-
- {MENU_DIETARY_TAGS.map(tag => ( - - ))} -
-
-
-

- {t('menuForm.allergenTags')} -

-
- {MENU_ALLERGEN_TAGS.map(tag => ( - - ))} -
-
-
-

- {t('menuForm.cuisine')} -

-
- {MENU_CUISINE_TAGS.map(tag => ( - - ))} -
+
+ +
+ {MENU_ALLERGENS.map(a => ( + + ))}
diff --git a/app/components/staff/menu/manual_menu/published_menu_panel.tsx b/app/components/staff/menu/manual_menu/published_menu_panel.tsx index d4bbd4e..dc932e0 100644 --- a/app/components/staff/menu/manual_menu/published_menu_panel.tsx +++ b/app/components/staff/menu/manual_menu/published_menu_panel.tsx @@ -3,7 +3,7 @@ import { useKeycloak } from '@react-keycloak/web'; import { useNavigate } from 'react-router-dom'; import { Plus } from 'lucide-react'; import { menuService, type PublishedMenu } from '~/api/menu_service'; -import type { DailyMenu, Dish, TagValue } from '~/interfaces'; +import type { DailyMenu, Dish, Allergen } from '~/interfaces'; import { LoadingSpinner } from '~/components/staff/common/loading_spinner'; import { ErrorState, EmptyState } from '~/components/staff/common/error_state'; import { StatusBanner } from '~/components/staff/common/status_banner'; @@ -63,7 +63,7 @@ export function PublishedMenuPanel({ restaurantId }: PublishedMenuPanelProps) { dishes: (item.dishes ?? []).map((dish: any) => ({ ...dish, category: dish.category ?? 'MAIN_COURSE', - tags: Array.isArray(dish.tags) ? dish.tags.map((t: any) => t.value ?? t) : [], + allergens: Array.isArray(dish.allergens) ? dish.allergens : [], price: typeof dish.price === 'string' ? dish.price : String(dish.price ?? ''), })), }); @@ -137,15 +137,17 @@ export function PublishedMenuPanel({ restaurantId }: PublishedMenuPanelProps) { }); }; - const handleTagToggle = (dishIndex: number, tag: TagValue) => { + const handleAllergenToggle = (dishIndex: number, allergen: Allergen) => { if (!menu || !menu.dishes) return; - const dish = menu.dishes[dishIndex] as any; - const tags = dish.tags || []; + const dish = menu.dishes[dishIndex]; + const allergens = dish.allergens || []; - const updatedTags = tags.includes(tag) ? tags.filter((t: string) => t !== tag) : [...tags, tag]; + const updatedAllergens = allergens.includes(allergen) + ? allergens.filter(a => a !== allergen) + : [...allergens, allergen]; - handleDishChange(dishIndex, 'tags' as any, updatedTags); + handleDishChange(dishIndex, 'allergens', updatedAllergens); }; const handleRemoveDish = (index: number) => { @@ -172,7 +174,7 @@ export function PublishedMenuPanel({ restaurantId }: PublishedMenuPanelProps) { image: '', category: 'MAIN_COURSE', price: '0', - tags: [], + allergens: [], } as any, ], }); @@ -216,7 +218,7 @@ export function PublishedMenuPanel({ restaurantId }: PublishedMenuPanelProps) { ...dish, category: dish.category ?? 'MAIN_COURSE', price: String(typeof dish.price === 'number' ? dish.price : Number(dish.price ?? 0)), - tags: (dish as any).tags ?? [], + allergens: dish.allergens ?? [], })), }; @@ -373,7 +375,7 @@ export function PublishedMenuPanel({ restaurantId }: PublishedMenuPanelProps) { dish={dish as any} index={index} onDishChange={handleDishChange} - onTagToggle={handleTagToggle} + onAllergenToggle={handleAllergenToggle} onRemove={handleRemoveDish} /> ))} diff --git a/app/components/staff/menu/menu_draft.tsx b/app/components/staff/menu/menu_draft.tsx index f0c596c..295dfbc 100644 --- a/app/components/staff/menu/menu_draft.tsx +++ b/app/components/staff/menu/menu_draft.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { useKeycloak } from '@react-keycloak/web'; import { useNavigate } from 'react-router-dom'; import { menuService } from '~/api/menu_service'; -import type { DailyMenu, Dish, TagValue } from '~/interfaces'; +import type { DailyMenu, Dish, Allergen } from '~/interfaces'; import { LoadingSpinner } from '~/components/staff/common/loading_spinner'; import { ErrorState, EmptyState } from '~/components/staff/common/error_state'; import { StatusBanner } from '~/components/staff/common/status_banner'; @@ -66,15 +66,17 @@ export function StaffMenuDraft({ restaurantId }: StaffMenuDraftProps) { }); }; - const handleTagToggle = (dishIndex: number, tag: TagValue) => { + const handleAllergenToggle = (dishIndex: number, allergen: Allergen) => { if (!menu || !menu.dishes) return; - const dish = menu.dishes[dishIndex] as any; - const tags = dish.tags || []; + const dish = menu.dishes[dishIndex]; + const allergens = dish.allergens || []; - const updatedTags = tags.includes(tag) ? tags.filter((t: string) => t !== tag) : [...tags, tag]; + const updatedAllergens = allergens.includes(allergen) + ? allergens.filter(a => a !== allergen) + : [...allergens, allergen]; - handleDishChange(dishIndex, 'tags' as any, updatedTags); + handleDishChange(dishIndex, 'allergens', updatedAllergens); }; const handleRemoveDish = (index: number) => { @@ -143,7 +145,7 @@ export function StaffMenuDraft({ restaurantId }: StaffMenuDraftProps) { dish={dish} index={index} onDishChange={handleDishChange} - onTagToggle={handleTagToggle} + onAllergenToggle={handleAllergenToggle} onRemove={handleRemoveDish} /> ))} diff --git a/app/i18n.ts b/app/i18n.ts index fa7fef3..a6ee009 100644 --- a/app/i18n.ts +++ b/app/i18n.ts @@ -41,7 +41,7 @@ const resources = { restaurants: 'Restaurants', restaurantsTitle: 'Restaurants', restaurantsSubtitle: - "Explore today's open spots. Filter based on available dietary options today.", + 'Explore today’s open spots. Filter based on available dietary options today.', vendingMachines: 'Vending Machines', vendingMachinesTitle: 'Vending Machines', vendingMachinesSubtitle: 'Browse available vending machines and jump straight to the map.', @@ -66,24 +66,17 @@ const resources = { toggleLactoseFree: 'Toggle lactose free', toggleGlutenFree: 'Toggle gluten free', toggleHasMenuToday: 'Toggle menu for today', - nutsFree: 'Nuts-free', - italian: 'Italian', - polish: 'Polish', - asian: 'Asian', - fastFood: 'Fast food', - preferSection: 'I prefer', - avoidSection: 'I avoid', }, menu: { dishesTitle: '{{name}}', - dishesSubtitle: "Explore today's dishes and filter by your preferences.", + dishesSubtitle: 'Explore today’s dishes and filter by your preferences.', loadingMenu: 'Loading menu...', errorLoadingMenu: 'Error loading menu:', noMenuYet: 'This restaurant has not published a menu yet.', noDishes: 'No dishes available for this restaurant', restaurantName: 'Restaurant: {{name}}', restaurantId: 'Restaurant ID: {{id}}', - todayMenuSubtitle: "Explore today's dishes and filter by your preferences.", + todayMenuSubtitle: 'Explore today’s dishes and filter by your preferences.', }, profile: { pageTitle: 'Your profile', @@ -129,18 +122,6 @@ const resources = { errorCreatingRestaurant: 'An error occurred while creating the restaurant.', failedToUpdateRestaurant: 'Failed to update restaurant. Please try again.', errorUpdatingRestaurant: 'An error occurred while updating the restaurant.', - preferences: 'Preferences', - preferencesSubtitle: 'Restaurants will be sorted based on your preferences.', - iPrefer: 'I prefer', - iAvoid: 'I avoid', - savePreferences: 'Save preferences', - preferencesSaved: 'Saved!', - failedToSavePreferences: 'Failed to save preferences. Please try again.', - loadingPreferences: 'Loading preferences…', - tagItalian: 'Italian', - tagPolish: 'Polish', - tagAsian: 'Asian', - tagFastFood: 'Fast food', }, manager: { panelTitle: 'Manager panel', @@ -200,10 +181,7 @@ const resources = { category: 'Category', selectCategory: 'Select category', price: 'Price', - allergens: 'Ingredients', - cuisine: 'Cuisine', - dietaryTags: 'Dietary', - allergenTags: 'Ingredients', + allergens: 'Allergens', allergenGluten: 'Gluten', allergenLactose: 'Lactose', allergenMeat: 'Meat', @@ -363,13 +341,6 @@ const resources = { toggleLactoseFree: 'Przełącz filtr bez laktozy', toggleGlutenFree: 'Przełącz filtr bezglutenowy', toggleHasMenuToday: 'Przełącz ma menu na dziś', - nutsFree: 'Bez orzechów', - italian: 'Włoska', - polish: 'Polska', - asian: 'Azjatycka', - fastFood: 'Fast food', - preferSection: 'Preferuję', - avoidSection: 'Unikam', }, menu: { dishesTitle: '{{name}}', @@ -426,18 +397,6 @@ const resources = { errorCreatingRestaurant: 'Wystąpił błąd podczas tworzenia restauracji.', failedToUpdateRestaurant: 'Nie udało się zaktualizować restauracji. Spróbuj ponownie.', errorUpdatingRestaurant: 'Wystąpił błąd podczas aktualizacji restauracji.', - preferences: 'Preferencje', - preferencesSubtitle: 'Restauracje będą sortowane na podstawie Twoich preferencji.', - iPrefer: 'Preferuję', - iAvoid: 'Unikam', - savePreferences: 'Zapisz preferencje', - preferencesSaved: 'Zapisano!', - failedToSavePreferences: 'Nie udało się zapisać preferencji. Spróbuj ponownie.', - loadingPreferences: 'Ładowanie preferencji…', - tagItalian: 'Włoska', - tagPolish: 'Polska', - tagAsian: 'Azjatycka', - tagFastFood: 'Fast food', }, manager: { panelTitle: 'Panel menedżera', @@ -498,10 +457,7 @@ const resources = { category: 'Kategoria', selectCategory: 'Wybierz kategorię', price: 'Cena', - allergens: 'Składniki', - cuisine: 'Kuchnia', - dietaryTags: 'Dieta', - allergenTags: 'Składniki', + allergens: 'Alergeny', allergenGluten: 'Gluten', allergenLactose: 'Laktoza', allergenMeat: 'Mięso', diff --git a/app/interfaces.tsx b/app/interfaces.tsx index 4382dd7..c40c172 100644 --- a/app/interfaces.tsx +++ b/app/interfaces.tsx @@ -41,43 +41,9 @@ export interface Restaurant { openNow: boolean; } -export type TagType = 'CUISINE' | 'ALLERGEN' | 'DIETARY'; -export type TagValue = - | 'ITALIAN' - | 'POLISH' - | 'ASIAN' - | 'FAST_FOOD' - | 'NUTS' - | 'GLUTEN' - | 'LACTOSE' - | 'SESAME' - | 'VEGAN' - | 'VEGETARIAN'; - -export interface Tag { - id: string; - value: TagValue; - tagType: TagType; - name: string; -} +export type Allergen = 'NUTS' | 'GLUTEN' | 'MEAT' | 'LACTOSE' | 'SESAME'; -export const MENU_ALLERGENS: TagValue[] = ['GLUTEN', 'LACTOSE', 'NUTS', 'VEGAN', 'VEGETARIAN']; -export const MENU_DIETARY_TAGS: TagValue[] = ['VEGAN', 'VEGETARIAN']; -export const MENU_ALLERGEN_TAGS: TagValue[] = ['GLUTEN', 'LACTOSE', 'NUTS', 'SESAME']; -export const MENU_CUISINE_TAGS: TagValue[] = ['ITALIAN', 'POLISH', 'ASIAN', 'FAST_FOOD']; - -export const TAG_ID_MAP: Record = { - ITALIAN: 'a0000000-0000-0000-0000-000000000001', - POLISH: 'a0000000-0000-0000-0000-000000000002', - ASIAN: 'a0000000-0000-0000-0000-000000000003', - FAST_FOOD: 'a0000000-0000-0000-0000-000000000004', - NUTS: 'a0000000-0000-0000-0000-000000000005', - GLUTEN: 'a0000000-0000-0000-0000-000000000006', - LACTOSE: 'a0000000-0000-0000-0000-000000000007', - SESAME: 'a0000000-0000-0000-0000-000000000010', - VEGAN: 'a0000000-0000-0000-0000-000000000008', - VEGETARIAN: 'a0000000-0000-0000-0000-000000000009', -}; +export const MENU_ALLERGENS: Allergen[] = ['GLUTEN', 'LACTOSE', 'MEAT', 'NUTS', 'SESAME']; export interface DailyMenu { id: string; @@ -98,11 +64,6 @@ export interface VendingMachine { export type Facility = Restaurant | VendingMachine; -export interface RankedRestaurant { - restaurant: Restaurant; - score: number; -} - export interface FacilityInfoProps { selectedPoint: Facility | null; onClose: () => void; @@ -116,7 +77,7 @@ export interface Dish { category?: string; price: string; image: string; - tags: Tag[]; + allergens: string[]; } export interface DishDTO { @@ -124,7 +85,7 @@ export interface DishDTO { name: string; category: string; price: number; - tags: string[]; + allergens: string[]; } export interface DailyMenuDTO { diff --git a/app/shadcn/lib/dish_filters.ts b/app/shadcn/lib/dish_filters.ts index 2c6101b..94e15ad 100644 --- a/app/shadcn/lib/dish_filters.ts +++ b/app/shadcn/lib/dish_filters.ts @@ -1,56 +1,45 @@ -import type { DailyMenu, Dish } from '~/interfaces'; +import type { DailyMenu, Dish } from "~/interfaces"; -function hasTag(dish: Dish, value: string): boolean { - return (dish.tags ?? []).some(t => t.value === value); +function normalizeAllergens(dish: Dish): string[] { + return (dish.allergens ?? []).map((a) => String(a).toLowerCase()); } export function isVeganDish(dish: Dish): boolean { - return hasTag(dish, 'VEGAN'); + const allergens = normalizeAllergens(dish); + if (allergens.length === 0) return true; + return !allergens.includes("meat") && !allergens.includes("lactose"); } export function isVegetarianDish(dish: Dish): boolean { - return hasTag(dish, 'VEGETARIAN'); + const allergens = normalizeAllergens(dish); + if (allergens.length === 0) return true; + return !allergens.includes("meat"); } export function isLactoseFreeDish(dish: Dish): boolean { - return !hasTag(dish, 'LACTOSE'); + const allergens = normalizeAllergens(dish); + if (allergens.length === 0) return true; + return !allergens.includes("lactose"); } export function isGlutenFreeDish(dish: Dish): boolean { - return !hasTag(dish, 'GLUTEN'); -} - -export function isNutsFreeDish(dish: Dish): boolean { - return !hasTag(dish, 'NUTS'); -} - -export function hasCuisineTag(dish: Dish, cuisine: string): boolean { - return hasTag(dish, cuisine); + const allergens = normalizeAllergens(dish); + if (allergens.length === 0) return true; + return !allergens.includes("gluten"); } export function matchesDishFilter(dish: Dish, filter: string): boolean { switch (filter) { - case 'vegan': + case "vegan": return isVeganDish(dish); - case 'vegetarian': + case "vegetarian": return isVegetarianDish(dish); - case 'lactose-free': - case 'lactoseFree': + case "lactose-free": + case "lactoseFree": return isLactoseFreeDish(dish); - case 'gluten-free': - case 'glutenFree': + case "gluten-free": + case "glutenFree": return isGlutenFreeDish(dish); - case 'nuts-free': - case 'nutsFree': - return isNutsFreeDish(dish); - case 'italian': - return hasCuisineTag(dish, 'ITALIAN'); - case 'polish': - return hasCuisineTag(dish, 'POLISH'); - case 'asian': - return hasCuisineTag(dish, 'ASIAN'); - case 'fastFood': - return hasCuisineTag(dish, 'FAST_FOOD'); default: return true; } @@ -62,7 +51,7 @@ export function filterDishes( ): Dish[] { if (!dishes) return []; if (!filters || filters.length === 0) return dishes; - return dishes.filter(d => filters.every(f => matchesDishFilter(d, f))); + return dishes.filter((d) => filters.every((f) => matchesDishFilter(d, f))); } export function hasVeganOption(menu: DailyMenu | null): boolean { @@ -83,14 +72,4 @@ export function hasLactoseFreeOption(menu: DailyMenu | null): boolean { export function hasGlutenFreeOption(menu: DailyMenu | null): boolean { if (!menu?.dishes) return false; return menu.dishes.some(isGlutenFreeDish); -} - -export function hasNutsFreeOption(menu: DailyMenu | null): boolean { - if (!menu?.dishes) return false; - return menu.dishes.some(isNutsFreeDish); -} - -export function hasCuisineOption(menu: DailyMenu | null, cuisine: string): boolean { - if (!menu?.dishes) return false; - return menu.dishes.some(d => hasCuisineTag(d, cuisine)); -} +} \ No newline at end of file diff --git a/app/shadcn/lib/restaurant_filters.ts b/app/shadcn/lib/restaurant_filters.ts index 0791a9f..5dcea72 100644 --- a/app/shadcn/lib/restaurant_filters.ts +++ b/app/shadcn/lib/restaurant_filters.ts @@ -1,41 +1,42 @@ -import type { DailyMenu, Dish } from '~/interfaces'; - -function hasTag(dish: Dish, value: string): boolean { - return (dish.tags ?? []).some(t => t.value === value); -} +import type { DailyMenu, Dish } from "~/interfaces" function isVeganDish(dish: Dish): boolean { - return hasTag(dish, 'VEGAN'); + if (!dish.allergens || dish.allergens.length === 0) return true + return !dish.allergens.includes("MEAT") && + !dish.allergens.includes("LACTOSE") } function isVegetarianDish(dish: Dish): boolean { - return hasTag(dish, 'VEGETARIAN'); + if (!dish.allergens || dish.allergens.length === 0) return true + return !dish.allergens.includes("MEAT") } function isLactoseFreeDish(dish: Dish): boolean { - return !hasTag(dish, 'LACTOSE'); + if (!dish.allergens || dish.allergens.length === 0) return true + return !dish.allergens.includes("LACTOSE") } function isGlutenFreeDish(dish: Dish): boolean { - return !hasTag(dish, 'GLUTEN'); + if (!dish.allergens || dish.allergens.length === 0) return true + return !dish.allergens.includes("GLUTEN") } export function hasVeganOption(menu: DailyMenu | null): boolean { - if (!menu?.dishes) return false; - return menu.dishes.some(isVeganDish); + if (!menu?.dishes) return false + return menu.dishes.some(isVeganDish) } export function hasVegetarianOption(menu: DailyMenu | null): boolean { - if (!menu?.dishes) return false; - return menu.dishes.some(isVegetarianDish); + if (!menu?.dishes) return false + return menu.dishes.some(isVegetarianDish) } export function hasLactoseFreeOption(menu: DailyMenu | null): boolean { - if (!menu?.dishes) return false; - return menu.dishes.some(isLactoseFreeDish); + if (!menu?.dishes) return false + return menu.dishes.some(isLactoseFreeDish) } export function hasGlutenFreeOption(menu: DailyMenu | null): boolean { - if (!menu?.dishes) return false; - return menu.dishes.some(isGlutenFreeDish); + if (!menu?.dishes) return false + return menu.dishes.some(isGlutenFreeDish) } diff --git a/public/silent-check-sso.html b/public/silent-check-sso.html deleted file mode 100644 index b80af7f..0000000 --- a/public/silent-check-sso.html +++ /dev/null @@ -1,8 +0,0 @@ - - - - - -