From 46cc32bba7ff1ee535554381d4dd80b7d4c76b93 Mon Sep 17 00:00:00 2001 From: David Petkov Date: Thu, 8 Aug 2024 11:33:15 +0300 Subject: [PATCH 01/45] Settings page rework and responsive --- src/client/CookingAppReact/src/App.jsx | 4 +- .../src/components/userMenu/UserMenu.jsx | 17 +- .../CookingAppReact/src/pages/rules/Rules.jsx | 8 + .../src/pages/settings/Settings.jsx | 193 ++++++++++++++++++ 4 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 src/client/CookingAppReact/src/pages/rules/Rules.jsx create mode 100644 src/client/CookingAppReact/src/pages/settings/Settings.jsx diff --git a/src/client/CookingAppReact/src/App.jsx b/src/client/CookingAppReact/src/App.jsx index 5958b44b..e65b507d 100644 --- a/src/client/CookingAppReact/src/App.jsx +++ b/src/client/CookingAppReact/src/App.jsx @@ -9,8 +9,9 @@ import Recipe from "./pages/recipe/Recipe"; import Admin from "./pages/admin/Admin"; import Subscribtion from "./pages/subscribtion/Subscribtion"; import Success from "./pages/subscribtion/Succes"; -import Settings from "./components/userMenu/settings/Settings"; +import Settings from "./pages/settings/Settings"; import SubscribtionDetails from "./pages/subscribtion/SubscribtionDetails"; +import Rules from "./pages/rules/Rules"; import NotFound from "./pages/error/NotFound"; const router = createBrowserRouter([ { @@ -31,6 +32,7 @@ const router = createBrowserRouter([ { path: "subscription/manage", element: }, { path: "success", element: }, { path: "settings", element: }, + { path: "/rules-and-policies", element: }, ], errorElement: , }, diff --git a/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx b/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx index c0bb8b5e..23bcecc1 100644 --- a/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx +++ b/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx @@ -4,6 +4,7 @@ import { useNavigate } from "react-router-dom"; import { useSelector } from "react-redux"; import { Cog6ToothIcon } from "@heroicons/react/24/outline"; import { CreditCardIcon } from "@heroicons/react/24/outline"; +import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; import SignOutButton from "../auth/SignOutButton"; const UserMenu = ({ isOpen, toggleDropDown }) => { @@ -15,7 +16,7 @@ const UserMenu = ({ isOpen, toggleDropDown }) => { return (
event.stopPropagation()} - className={`absolute right-2 top-12 w-44 + className={`absolute right-2 top-12 w-56 ${isDarkTheme ? "bg-[#2F2F2F]" : "bg-white"} border border-gray-300 rounded-3xl shadow-sm z-20`} > @@ -49,6 +50,20 @@ const UserMenu = ({ isOpen, toggleDropDown }) => { Settings
+
{ navigate("/rules-and-policies"); toggleDropDown(); }} + title="Settings" + > + + + Rules & Policies + +

diff --git a/src/client/CookingAppReact/src/pages/rules/Rules.jsx b/src/client/CookingAppReact/src/pages/rules/Rules.jsx new file mode 100644 index 00000000..31e5dac5 --- /dev/null +++ b/src/client/CookingAppReact/src/pages/rules/Rules.jsx @@ -0,0 +1,8 @@ +export default function Rules() { + + return ( +
+ AAAAAAAAAAAAAAAAAAAAAAAAAA +
+ ); +} diff --git a/src/client/CookingAppReact/src/pages/settings/Settings.jsx b/src/client/CookingAppReact/src/pages/settings/Settings.jsx new file mode 100644 index 00000000..0f857714 --- /dev/null +++ b/src/client/CookingAppReact/src/pages/settings/Settings.jsx @@ -0,0 +1,193 @@ +import { UserIcon } from "@heroicons/react/24/outline"; +import { ClipboardDocumentIcon } from "@heroicons/react/24/outline"; +import { useState } from "react"; + +export default function Settings() { + const possibleAllergens = [ + "Peanuts", "Soy", "Egg", "Milk", "Fish", "Wheat", "Shellfish", "Tree nuts", + "Sesame", "Mustard", "Celery", "Molluscs", "Sulphites", "Nuts", "Ketchup", "Onion", "Garlic" + ]; + const [alergens, setAlergens] = useState([]); + const [foodPreferences, setFoodPreferences] = useState([]); + const [alergenInput, setAlergenInput] = useState(""); + const [foodPreferenceInput, setFoodPreferenceInput] = useState(""); + const [selectedPreference, setSelectedPreference] = useState("none"); + const [error, setError] = useState(""); + const [foodError, setFoodError] = useState(""); + + const handleAddAlergen = () => { + const normalizedInput = alergenInput.trim().toLowerCase(); + const normalizedAllergens = possibleAllergens.map(alergen => alergen.toLowerCase()); + + if (alergenInput.trim() !== "") { + if (!normalizedAllergens.includes(normalizedInput)) { + setError("Allergen not found."); + } else if (alergens.length >= 12) { + setError("You can add a maximum of 12 allergens."); + } else if (alergens.map(alergen => alergen.toLowerCase()).includes(normalizedInput)) { + setError("Allergen already added."); + } else { + setAlergens(prevAlergens => [...prevAlergens, alergenInput.trim()]); + setAlergenInput(""); + setError(""); + } + } + }; + + const handleAddFoodPreference = () => { + if (foodPreferenceInput.trim() !== "") { + if (foodPreferences.includes(foodPreferenceInput.trim())) { + setFoodError("Food preference already added."); + } else { + setFoodPreferences(prevFoodPreferences => [...prevFoodPreferences, foodPreferenceInput.trim()]); + setFoodPreferenceInput(""); + setFoodError(""); + } + } + }; + + const handleRemoveAlergen = indexToRemove => { + setAlergens(prevAlergens => prevAlergens.filter((_, index) => index !== indexToRemove)); + setError(""); + }; + + const handleRemoveFoodPreference = indexToRemove => { + setFoodPreferences(prevFoodPreferences => prevFoodPreferences.filter((_, index) => index !== indexToRemove)); + setFoodError(""); + }; + + const handleAddAlergenPressEnter = event => { + if (event.key === "Enter") { + event.preventDefault(); + handleAddAlergen(); + } + }; + + const handleAddDislikeFoodsPressEnter = event => { + if (event.key === "Enter") { + event.preventDefault(); + handleAddFoodPreference(); + } + }; + + return ( +
+

Profile

+
+
+ +
+

David Petkov

+

+ Copy User Id +

+
+
+
+ +
+
+ +
+

Language and Theme

+
+ + +
+ +

Food Preferences

+ {/* Dietary Preference Section */} +
+ +
+
+
+

Allergens

+ {alergens.length > 0 ? ( +
+ {alergens.map((alergen, index) => ( + + ))} +
+ ) : ( +

None added

+ )} + setAlergenInput(e.target.value)} + onKeyDown={handleAddAlergenPressEnter} + className="border rounded-lg px-4 py-2 mb-2 w-full border-gray-300 bg-white text-black" + placeholder="Add your allergens" /> + + {possibleAllergens.map((alergen, index) => ( + + {error &&

{error}

} + +
+
+

Disliked Foods

+ {foodPreferences.length > 0 ? ( +
+ {foodPreferences.map((preference, index) => ( + + ))} +
+ ) : ( +

None added

+ )} + setFoodPreferenceInput(e.target.value)} + onKeyDown={handleAddDislikeFoodsPressEnter} + className="border rounded-lg px-4 py-2 mb-2 w-full border-gray-300 bg-white text-black" + placeholder="Add your disliked foods" /> + {foodError &&

{foodError}

} + +
+
+
+ +
+
+
+ ); +} From 4f86448cc00a231c6f42b978a0a86fc44788519d Mon Sep 17 00:00:00 2001 From: David Petkov Date: Thu, 8 Aug 2024 11:56:32 +0300 Subject: [PATCH 02/45] Sidebars responsive --- .../src/pages/settings/Settings.jsx | 129 ++++++++++-------- .../CookingAppReact/src/store/uiSlice.js | 18 ++- 2 files changed, 81 insertions(+), 66 deletions(-) diff --git a/src/client/CookingAppReact/src/pages/settings/Settings.jsx b/src/client/CookingAppReact/src/pages/settings/Settings.jsx index 0f857714..b5f22281 100644 --- a/src/client/CookingAppReact/src/pages/settings/Settings.jsx +++ b/src/client/CookingAppReact/src/pages/settings/Settings.jsx @@ -1,77 +1,86 @@ import { UserIcon } from "@heroicons/react/24/outline"; import { ClipboardDocumentIcon } from "@heroicons/react/24/outline"; import { useState } from "react"; +import { useSelector } from "react-redux"; export default function Settings() { - const possibleAllergens = [ - "Peanuts", "Soy", "Egg", "Milk", "Fish", "Wheat", "Shellfish", "Tree nuts", - "Sesame", "Mustard", "Celery", "Molluscs", "Sulphites", "Nuts", "Ketchup", "Onion", "Garlic" - ]; - const [alergens, setAlergens] = useState([]); - const [foodPreferences, setFoodPreferences] = useState([]); - const [alergenInput, setAlergenInput] = useState(""); - const [foodPreferenceInput, setFoodPreferenceInput] = useState(""); - const [selectedPreference, setSelectedPreference] = useState("none"); - const [error, setError] = useState(""); - const [foodError, setFoodError] = useState(""); + const isOpenRecipes = useSelector((state) => state.ui.recipesOpen); + const isOpenSideBar = useSelector((state) => state.ui.sidebarOpen); - const handleAddAlergen = () => { - const normalizedInput = alergenInput.trim().toLowerCase(); - const normalizedAllergens = possibleAllergens.map(alergen => alergen.toLowerCase()); + const possibleAllergens = [ + "Peanuts", "Soy", "Egg", "Milk", "Fish", "Wheat", "Shellfish", "Tree nuts", + "Sesame", "Mustard", "Celery", "Molluscs", "Sulphites", "Nuts", "Ketchup", "Onion", "Garlic" + ]; + const [alergens, setAlergens] = useState([]); + const [foodPreferences, setFoodPreferences] = useState([]); + const [alergenInput, setAlergenInput] = useState(""); + const [foodPreferenceInput, setFoodPreferenceInput] = useState(""); + const [selectedPreference, setSelectedPreference] = useState("none"); + const [error, setError] = useState(""); + const [foodError, setFoodError] = useState(""); - if (alergenInput.trim() !== "") { - if (!normalizedAllergens.includes(normalizedInput)) { - setError("Allergen not found."); - } else if (alergens.length >= 12) { - setError("You can add a maximum of 12 allergens."); - } else if (alergens.map(alergen => alergen.toLowerCase()).includes(normalizedInput)) { - setError("Allergen already added."); - } else { - setAlergens(prevAlergens => [...prevAlergens, alergenInput.trim()]); - setAlergenInput(""); - setError(""); - } - } - }; + const handleAddAlergen = () => { + const normalizedInput = alergenInput.trim().toLowerCase(); + const normalizedAllergens = possibleAllergens.map(alergen => alergen.toLowerCase()); - const handleAddFoodPreference = () => { - if (foodPreferenceInput.trim() !== "") { - if (foodPreferences.includes(foodPreferenceInput.trim())) { - setFoodError("Food preference already added."); - } else { - setFoodPreferences(prevFoodPreferences => [...prevFoodPreferences, foodPreferenceInput.trim()]); - setFoodPreferenceInput(""); - setFoodError(""); - } - } - }; + if (alergenInput.trim() !== "") { + if (!normalizedAllergens.includes(normalizedInput)) { + setError("Allergen not found."); + } else if (alergens.length >= 12) { + setError("You can add a maximum of 12 allergens."); + } else if (alergens.map(alergen => alergen.toLowerCase()).includes(normalizedInput)) { + setError("Allergen already added."); + } else { + setAlergens(prevAlergens => [...prevAlergens, alergenInput.trim()]); + setAlergenInput(""); + setError(""); + } + } + }; - const handleRemoveAlergen = indexToRemove => { - setAlergens(prevAlergens => prevAlergens.filter((_, index) => index !== indexToRemove)); - setError(""); - }; + const handleAddFoodPreference = () => { + if (foodPreferenceInput.trim() !== "") { + if (foodPreferences.includes(foodPreferenceInput.trim())) { + setFoodError("Food preference already added."); + } else { + setFoodPreferences(prevFoodPreferences => [...prevFoodPreferences, foodPreferenceInput.trim()]); + setFoodPreferenceInput(""); + setFoodError(""); + } + } + }; - const handleRemoveFoodPreference = indexToRemove => { - setFoodPreferences(prevFoodPreferences => prevFoodPreferences.filter((_, index) => index !== indexToRemove)); - setFoodError(""); - }; + const handleRemoveAlergen = indexToRemove => { + setAlergens(prevAlergens => prevAlergens.filter((_, index) => index !== indexToRemove)); + setError(""); + }; + + const handleRemoveFoodPreference = indexToRemove => { + setFoodPreferences(prevFoodPreferences => prevFoodPreferences.filter((_, index) => index !== indexToRemove)); + setFoodError(""); + }; - const handleAddAlergenPressEnter = event => { - if (event.key === "Enter") { - event.preventDefault(); - handleAddAlergen(); - } - }; + const handleAddAlergenPressEnter = event => { + if (event.key === "Enter") { + event.preventDefault(); + handleAddAlergen(); + } + }; - const handleAddDislikeFoodsPressEnter = event => { - if (event.key === "Enter") { - event.preventDefault(); - handleAddFoodPreference(); - } - }; + const handleAddDislikeFoodsPressEnter = event => { + if (event.key === "Enter") { + event.preventDefault(); + handleAddFoodPreference(); + } + }; return ( -
+

Profile

diff --git a/src/client/CookingAppReact/src/store/uiSlice.js b/src/client/CookingAppReact/src/store/uiSlice.js index de1e4b47..6c408730 100644 --- a/src/client/CookingAppReact/src/store/uiSlice.js +++ b/src/client/CookingAppReact/src/store/uiSlice.js @@ -28,22 +28,28 @@ const uiSlice = createSlice({ reducers: { openSidebar(state) { state.sidebarOpen = true; - }, - setInput(state, action) { - state.input = action.payload; + if (window.innerWidth < 1300) { + state.recipesOpen = false; + } }, closeSidebar(state) { state.sidebarOpen = false; }, + toggleRecipes(state) { + state.recipesOpen = !state.recipesOpen; + if (window.innerWidth < 1300 && state.recipesOpen) { + state.sidebarOpen = false; + } + }, + setInput(state, action) { + state.input = action.payload; + }, showToast(state, action) { state.toastMealId = action.payload; }, hideToast(state) { state.toastMealId = null; }, - toggleRecipes(state) { - state.recipesOpen = !state.recipesOpen; - }, setActive(state, action) { state.activeChat = action.payload; }, From 4367cf91788bdf77ec35bb3ba649cbe75091d62b Mon Sep 17 00:00:00 2001 From: Ekrem Beytula Date: Thu, 8 Aug 2024 14:32:47 +0300 Subject: [PATCH 03/45] Saving user preferences --- .../settings/DietaryPreferences.jsx | 109 ++++++++++ .../components/settings/FoodPreferences.jsx | 25 +++ .../src/components/settings/Preferences.jsx | 162 ++++++++++++++ .../src/components/settings/UserInterface.jsx | 59 ++++++ .../src/hooks/useFetchUserStatus.js | 48 ++--- .../src/hooks/useFoodPreferences.js | 44 ++++ .../src/hooks/useUiPreferences.js | 28 +++ .../src/pages/settings/Settings.jsx | 200 ++---------------- .../CookingAppReact/src/store/uiSlice.js | 4 +- 9 files changed, 470 insertions(+), 209 deletions(-) create mode 100644 src/client/CookingAppReact/src/components/settings/DietaryPreferences.jsx create mode 100644 src/client/CookingAppReact/src/components/settings/FoodPreferences.jsx create mode 100644 src/client/CookingAppReact/src/components/settings/Preferences.jsx create mode 100644 src/client/CookingAppReact/src/components/settings/UserInterface.jsx create mode 100644 src/client/CookingAppReact/src/hooks/useFoodPreferences.js create mode 100644 src/client/CookingAppReact/src/hooks/useUiPreferences.js diff --git a/src/client/CookingAppReact/src/components/settings/DietaryPreferences.jsx b/src/client/CookingAppReact/src/components/settings/DietaryPreferences.jsx new file mode 100644 index 00000000..d178c833 --- /dev/null +++ b/src/client/CookingAppReact/src/components/settings/DietaryPreferences.jsx @@ -0,0 +1,109 @@ +import { useState } from "react"; +import { IoClose } from "react-icons/io5"; +export default function DietaryPreferences({ + possibleAllergens, + error, + foodError, + allergens, + removeAllergen, + alergenInput, + setAlergenInput, + handleRemoveFoodPreference, + handleAddAlergenPressEnter, + handleAddAlergen, + foodPreferences, + foodPreferenceInput, + setFoodPreferenceInput, + handleAddDislikeFoodsPressEnter, + handleAddFoodPreference, +}) { + return ( +
+
+

Allergens

+ {allergens.length > 0 ? ( +
+ {allergens.map((alergen, index) => ( + + ))} +
+ ) : ( +

None added

+ )} + setAlergenInput(e.target.value)} + onKeyDown={handleAddAlergenPressEnter} + className="border rounded-lg px-4 py-2 mb-2 w-full border-gray-300 bg-white text-black" + placeholder="Add your allergens" + /> + + {possibleAllergens.map((alergen, index) => ( + + {error &&

{error}

} + +
+
+

+ Disliked Foods +

+ {foodPreferences.length > 0 ? ( +
+ {foodPreferences.map((preference, index) => ( + + ))} +
+ ) : ( +

None added

+ )} + setFoodPreferenceInput(e.target.value)} + onKeyDown={handleAddDislikeFoodsPressEnter} + className="border rounded-lg px-4 py-2 mb-2 w-full border-gray-300 bg-white text-black" + placeholder="Add your disliked foods" + /> + {foodError && ( +

{foodError}

+ )} + +
+
+ ); +} diff --git a/src/client/CookingAppReact/src/components/settings/FoodPreferences.jsx b/src/client/CookingAppReact/src/components/settings/FoodPreferences.jsx new file mode 100644 index 00000000..62cad201 --- /dev/null +++ b/src/client/CookingAppReact/src/components/settings/FoodPreferences.jsx @@ -0,0 +1,25 @@ +import { useState } from "react"; + +export default function FoodPreferences({ + selectedPreference, + setSelectedPreference, +}) { + return ( + <> +

Food Preferences

+
+ +
+ + ); +} diff --git a/src/client/CookingAppReact/src/components/settings/Preferences.jsx b/src/client/CookingAppReact/src/components/settings/Preferences.jsx new file mode 100644 index 00000000..4eff0f8e --- /dev/null +++ b/src/client/CookingAppReact/src/components/settings/Preferences.jsx @@ -0,0 +1,162 @@ +import useFoodPreferences from "@/hooks/useFoodPreferences"; +import DietaryPreferences from "./DietaryPreferences"; +import FoodPreferences from "./FoodPreferences"; +import UserInterface from "./UserInterface"; +import { useEffect, useState } from "react"; +import { getToken } from "@/msal/msal"; +import { jwtDecode } from "jwt-decode"; +import { useSelect } from "@material-tailwind/react"; +import useFetchUserStatus from "../../hooks/useFetchUserStatus"; +import { useSelector } from "react-redux"; +import { useDispatch } from "react-redux"; +import { checkUserStatus } from "@/http/user"; +export default function Preferences() { + const possibleAllergens = [ + "Peanuts", + "Soy", + "Egg", + "Milk", + "Fish", + "Wheat", + "Shellfish", + "Tree nuts", + "Sesame", + "Mustard", + "Celery", + "Molluscs", + "Sulphites", + "Nuts", + "Ketchup", + "Onion", + "Garlic", + ]; + const dietaryPreferences = useSelector( + (state) => state.user.dietaryPreferences + ); + const [foodPreferenceInput, setFoodPreferenceInput] = useState(""); + const [selectedPreference, setSelectedPreference] = useState("none"); + + const [allergens, setAlergens] = useState([]); + const [alergenInput, setAlergenInput] = useState(""); + const [foodPreferences, setFoodPreferences] = useState([]); + const [foodError, setFoodError] = useState(""); + const [error, setError] = useState(""); + const { save, isSaving } = useFoodPreferences(); + useEffect(() => { + console.log(dietaryPreferences.dietaryPreference); + setAlergens(dietaryPreferences.allergies); + setFoodPreferences(dietaryPreferences.avoidedFoods); + setSelectedPreference(dietaryPreferences.dietaryPreference); + }, [dietaryPreferences]); + const handleRemoveAlergen = (indexToRemove) => { + setAlergens((prevAlergens) => + prevAlergens.filter((_, index) => index !== indexToRemove) + ); + setError(""); + }; + const handleRemoveFoodPreference = (indexToRemove) => { + setFoodPreferences((prevFoodPreferences) => + prevFoodPreferences.filter((_, index) => index !== indexToRemove) + ); + setFoodError(""); + }; + + const handleAddAlergen = () => { + const normalizedInput = alergenInput.trim().toLowerCase(); + const normalizedAllergens = possibleAllergens.map((alergen) => + alergen.toLowerCase() + ); + + if (alergenInput.trim() !== "") { + if (!normalizedAllergens.includes(normalizedInput)) { + setError("Allergen not found."); + } else if (allergens.length >= 12) { + setError("You can add a maximum of 12 allergens."); + } else if ( + allergens + .map((alergen) => alergen.toLowerCase()) + .includes(normalizedInput) + ) { + setError("Allergen already added."); + } else { + setAlergens((prevAlergens) => [...prevAlergens, alergenInput.trim()]); + setAlergenInput(""); + setError(""); + } + } + }; + + const handleAddFoodPreference = () => { + if (foodPreferenceInput.trim() !== "") { + if (foodPreferences.includes(foodPreferenceInput.trim())) { + setFoodError("Food preference already added."); + } else { + setFoodPreferences((prevFoodPreferences) => [ + ...prevFoodPreferences, + foodPreferenceInput.trim(), + ]); + setFoodPreferenceInput(""); + setFoodError(""); + } + } + }; + + const handleAddAlergenPressEnter = (event) => { + if (event.key === "Enter") { + event.preventDefault(); + handleAddAlergen(); + } + }; + + const handleAddDislikeFoodsPressEnter = (event) => { + if (event.key === "Enter") { + event.preventDefault(); + handleAddFoodPreference(); + } + }; + async function savePreferences() { + const token = await getToken(); + const decoded = jwtDecode(token); + save({ + token: token, + userId: decoded.sub, + allergies: allergens, + avoidedfoods: foodPreferences, + dietaryPreference: selectedPreference, + }); + } + return ( +
+ + + +
+ +
+
+ ); +} diff --git a/src/client/CookingAppReact/src/components/settings/UserInterface.jsx b/src/client/CookingAppReact/src/components/settings/UserInterface.jsx new file mode 100644 index 00000000..c8adc75b --- /dev/null +++ b/src/client/CookingAppReact/src/components/settings/UserInterface.jsx @@ -0,0 +1,59 @@ +import useUiPreferences from "@/hooks/useUiPreferences"; +import { getToken } from "@/msal/msal"; +import { uiActions } from "@/store/uiSlice"; +import { jwtDecode } from "jwt-decode"; +import { useSelector } from "react-redux"; +import { useDispatch } from "react-redux"; +export default function UserInterface() { + const theme = useSelector((state) => state.ui.theme); + const language = useSelector((state) => state.ui.lang); + + const { changeUi } = useUiPreferences(); + async function langChange(event) { + const token = await getToken(); + const decoded = jwtDecode(token); + changeUi({ + token: token, + userId: decoded.sub, + theme: theme, + language: event.target.value, + }); + } + async function themeChange(event) { + const token = await getToken(); + const decoded = jwtDecode(token); + changeUi({ + token: token, + userId: decoded.sub, + theme: event.target.value, + language: language, + }); + } + return ( + <> +

Language and Theme

+
+ + +
+ + ); +} diff --git a/src/client/CookingAppReact/src/hooks/useFetchUserStatus.js b/src/client/CookingAppReact/src/hooks/useFetchUserStatus.js index f8b950e7..3dda5336 100644 --- a/src/client/CookingAppReact/src/hooks/useFetchUserStatus.js +++ b/src/client/CookingAppReact/src/hooks/useFetchUserStatus.js @@ -8,35 +8,27 @@ import { userActions } from "../store/userSlice"; const useFetchUserStatus = () => { const isInitial = useSelector((state) => state.ui.isInitial); const dispatch = useDispatch(); - - useEffect(() => { - const fetchToken = async () => { - const token = await getToken(); - if (isInitial) { - const response = await checkUserStatus({ token }); - if (response.status !== 401) { - const body = await response.json(); - dispatch( - uiActions.setTheme( - body.data.interfacePreference.theme === "Light" ? false : true - ) - ); - dispatch( - uiActions.setLanguage(body.data.interfacePreference.language) - ); - dispatch(userActions.setRole(body.data.role)); - dispatch( - userActions.setDietaryPreferences({ - allergies: body.data.allergies, - avoidedFoods: body.data.avoidedFoods, - dietaryPreference: body.data.dietaryPreference, - }) - ); - dispatch(uiActions.setIsInitial(false)); - } + const fetchToken = async () => { + const token = await getToken(); + if (isInitial) { + const response = await checkUserStatus({ token }); + if (response.status !== 401) { + const body = await response.json(); + dispatch(uiActions.setTheme(body.data.interfacePreference.theme)); + dispatch(uiActions.setLanguage(body.data.interfacePreference.language)); + dispatch(userActions.setRole(body.data.role)); + dispatch( + userActions.setDietaryPreferences({ + allergies: body.data.allergies, + avoidedFoods: body.data.avoidedFoods, + dietaryPreference: body.data.dietaryPreference, + }) + ); + dispatch(uiActions.setIsInitial(false)); } - }; - + } + }; + useEffect(() => { fetchToken(); }, [isInitial, dispatch]); }; diff --git a/src/client/CookingAppReact/src/hooks/useFoodPreferences.js b/src/client/CookingAppReact/src/hooks/useFoodPreferences.js new file mode 100644 index 00000000..8e392d2e --- /dev/null +++ b/src/client/CookingAppReact/src/hooks/useFoodPreferences.js @@ -0,0 +1,44 @@ +import { useMutation } from "@tanstack/react-query"; +import { useDispatch, useSelector } from "react-redux"; +import { preferences } from "../http/user"; +import { getToken } from "@/msal/msal"; +import { userActions } from "@/store/userSlice"; +import { checkUserStatus } from "../http/user"; +import toast from "react-hot-toast"; +const useFoodPreferences = () => { + const dispatch = useDispatch(); + const selectedChat = useSelector((state) => state.user.selectedChat); + + const { + mutate: save, + isPending: isSaving, + isError, + error, + } = useMutation({ + mutationFn: preferences, + onSuccess: async () => { + toast.success("Preferences saved!", { position: "top-center" }); + const token = await getToken(); + const response = await checkUserStatus({ token }); + if (response.status !== 401) { + const body = await response.json(); + dispatch( + userActions.setDietaryPreferences({ + allergies: body.data.allergies, + avoidedFoods: body.data.avoidedFoods, + dietaryPreference: body.data.dietaryPreference, + }) + ); + } + }, + }); + + return { + save, + isSaving, + isError, + error, + }; +}; + +export default useFoodPreferences; diff --git a/src/client/CookingAppReact/src/hooks/useUiPreferences.js b/src/client/CookingAppReact/src/hooks/useUiPreferences.js new file mode 100644 index 00000000..13c21b26 --- /dev/null +++ b/src/client/CookingAppReact/src/hooks/useUiPreferences.js @@ -0,0 +1,28 @@ +import { useMutation } from "@tanstack/react-query"; +import { setUi } from "../http/user"; +import { useDispatch } from "react-redux"; +import { uiActions } from "@/store/uiSlice"; +import { checkUserStatus } from "../http/user"; +import { getToken } from "@/msal/msal"; +const useUiPreferences = () => { + const dispatch = useDispatch(); + const { mutate: changeUi } = useMutation({ + mutationKey: ["setUi"], + mutationFn: setUi, + onSuccess: async () => { + const token = await getToken(); + const response = await checkUserStatus({ token }); + if (response.status !== 401) { + const body = await response.json(); + dispatch(uiActions.setTheme(body.data.interfacePreference.theme)); + dispatch(uiActions.setLanguage(body.data.interfacePreference.language)); + } + }, + }); + + return { + changeUi, + }; +}; + +export default useUiPreferences; diff --git a/src/client/CookingAppReact/src/pages/settings/Settings.jsx b/src/client/CookingAppReact/src/pages/settings/Settings.jsx index b5f22281..5d6be220 100644 --- a/src/client/CookingAppReact/src/pages/settings/Settings.jsx +++ b/src/client/CookingAppReact/src/pages/settings/Settings.jsx @@ -1,202 +1,44 @@ +import Preferences from "@/components/settings/Preferences"; import { UserIcon } from "@heroicons/react/24/outline"; import { ClipboardDocumentIcon } from "@heroicons/react/24/outline"; import { useState } from "react"; import { useSelector } from "react-redux"; export default function Settings() { - const isOpenRecipes = useSelector((state) => state.ui.recipesOpen); - const isOpenSideBar = useSelector((state) => state.ui.sidebarOpen); - - const possibleAllergens = [ - "Peanuts", "Soy", "Egg", "Milk", "Fish", "Wheat", "Shellfish", "Tree nuts", - "Sesame", "Mustard", "Celery", "Molluscs", "Sulphites", "Nuts", "Ketchup", "Onion", "Garlic" - ]; - const [alergens, setAlergens] = useState([]); - const [foodPreferences, setFoodPreferences] = useState([]); - const [alergenInput, setAlergenInput] = useState(""); - const [foodPreferenceInput, setFoodPreferenceInput] = useState(""); - const [selectedPreference, setSelectedPreference] = useState("none"); - const [error, setError] = useState(""); - const [foodError, setFoodError] = useState(""); - - const handleAddAlergen = () => { - const normalizedInput = alergenInput.trim().toLowerCase(); - const normalizedAllergens = possibleAllergens.map(alergen => alergen.toLowerCase()); - - if (alergenInput.trim() !== "") { - if (!normalizedAllergens.includes(normalizedInput)) { - setError("Allergen not found."); - } else if (alergens.length >= 12) { - setError("You can add a maximum of 12 allergens."); - } else if (alergens.map(alergen => alergen.toLowerCase()).includes(normalizedInput)) { - setError("Allergen already added."); - } else { - setAlergens(prevAlergens => [...prevAlergens, alergenInput.trim()]); - setAlergenInput(""); - setError(""); - } - } - }; - - const handleAddFoodPreference = () => { - if (foodPreferenceInput.trim() !== "") { - if (foodPreferences.includes(foodPreferenceInput.trim())) { - setFoodError("Food preference already added."); - } else { - setFoodPreferences(prevFoodPreferences => [...prevFoodPreferences, foodPreferenceInput.trim()]); - setFoodPreferenceInput(""); - setFoodError(""); - } - } - }; - - const handleRemoveAlergen = indexToRemove => { - setAlergens(prevAlergens => prevAlergens.filter((_, index) => index !== indexToRemove)); - setError(""); - }; - - const handleRemoveFoodPreference = indexToRemove => { - setFoodPreferences(prevFoodPreferences => prevFoodPreferences.filter((_, index) => index !== indexToRemove)); - setFoodError(""); - }; - - const handleAddAlergenPressEnter = event => { - if (event.key === "Enter") { - event.preventDefault(); - handleAddAlergen(); - } - }; - - const handleAddDislikeFoodsPressEnter = event => { - if (event.key === "Enter") { - event.preventDefault(); - handleAddFoodPreference(); - } - }; + const isOpenRecipes = useSelector((state) => state.ui.recipesOpen); + const isOpenSideBar = useSelector((state) => state.ui.sidebarOpen); return ( -
-

Profile

+
+

Profile

-

David Petkov

+

+ David Petkov +

Copy User Id

- -
-
- -
-

Language and Theme

-
- - -
- -

Food Preferences

- {/* Dietary Preference Section */} -
- -
-
-
-

Allergens

- {alergens.length > 0 ? ( -
- {alergens.map((alergen, index) => ( - - ))} -
- ) : ( -

None added

- )} - setAlergenInput(e.target.value)} - onKeyDown={handleAddAlergenPressEnter} - className="border rounded-lg px-4 py-2 mb-2 w-full border-gray-300 bg-white text-black" - placeholder="Add your allergens" /> - - {possibleAllergens.map((alergen, index) => ( - - {error &&

{error}

} - -
-
-

Disliked Foods

- {foodPreferences.length > 0 ? ( -
- {foodPreferences.map((preference, index) => ( - - ))} -
- ) : ( -

None added

- )} - setFoodPreferenceInput(e.target.value)} - onKeyDown={handleAddDislikeFoodsPressEnter} - className="border rounded-lg px-4 py-2 mb-2 w-full border-gray-300 bg-white text-black" - placeholder="Add your disliked foods" /> - {foodError &&

{foodError}

} - -
-
-
- +
+
); } diff --git a/src/client/CookingAppReact/src/store/uiSlice.js b/src/client/CookingAppReact/src/store/uiSlice.js index 6c408730..ab8ec0c3 100644 --- a/src/client/CookingAppReact/src/store/uiSlice.js +++ b/src/client/CookingAppReact/src/store/uiSlice.js @@ -9,7 +9,7 @@ const initialState = { input: "", isThinking: false, responseError: null, - isDarkTheme: false, + theme: "Light", photoUri: null, toastMealId: null, @@ -69,7 +69,7 @@ const uiSlice = createSlice({ state.isDarkTheme = !state.isDarkTheme; }, setTheme(state, action) { - state.isDarkTheme = action.payload; + state.theme = action.payload; }, setPhotoUri(state, action) { state.photoUri = action.payload; From 6766f18d9eeacb79b4a70fa77b296cde020b5785 Mon Sep 17 00:00:00 2001 From: David Petkov Date: Thu, 8 Aug 2024 18:19:55 +0300 Subject: [PATCH 04/45] Limitation Changes --- .../Common/Helpers/Profiles/CreateRole.cs | 8 +-- .../Services/Limitation/LimitationService.cs | 54 +++++++++++++------ 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/server/CookingApp/Common/Helpers/Profiles/CreateRole.cs b/src/server/CookingApp/Common/Helpers/Profiles/CreateRole.cs index e8e0875b..e94da4a6 100644 --- a/src/server/CookingApp/Common/Helpers/Profiles/CreateRole.cs +++ b/src/server/CookingApp/Common/Helpers/Profiles/CreateRole.cs @@ -16,8 +16,8 @@ public static Role Free() Limitations = new Limitations() { ChatFromDate = DateTime.Now, - ChatGeneration = 20, - RecipeGeneration = 0 + ChatGeneration = 10, + RecipeGeneration = 2 } }; } @@ -28,7 +28,9 @@ public static Role Premium() Type = RoleType.Premium, Limitations = new Limitations() { - RecipeGeneration = 20 + ChatFromDate = DateTime.Now, + ChatGeneration = 20, + RecipeGeneration = 30 } }; } diff --git a/src/server/CookingApp/Services/Limitation/LimitationService.cs b/src/server/CookingApp/Services/Limitation/LimitationService.cs index 26d334d3..141debc5 100644 --- a/src/server/CookingApp/Services/Limitation/LimitationService.cs +++ b/src/server/CookingApp/Services/Limitation/LimitationService.cs @@ -12,10 +12,37 @@ public async Task ProcessUserMessageLimitations(string userId) var user = await repo.GetFirstOrDefaultAsync(a => a.UserId == userId); ArgumentNullException.ThrowIfNull(user, nameof(user)); - if (user.Role.Type == RoleType.Premium || user.Role.Type == RoleType.Admin) + if (user.Role.Type == RoleType.Admin) { return ProcessResult.MessageLimitationSuccessfull; } + else if (user.Role.Type == RoleType.Premium) + { + var today = DateTime.UtcNow; + var chatDate = user.Role.Limitations.ChatFromDate; + ArgumentNullException.ThrowIfNull(chatDate, nameof(chatDate)); + var endDate = chatDate.Value.AddHours(5); + + if (today >= chatDate.Value && today <= endDate) + { + if (user.Role.Limitations.ChatGeneration > 0) + { + user.Role.Limitations.ChatGeneration--; + await repo.UpdateAsync(user); + return ProcessResult.MessageLimitationSuccessfull; + } + else + { + return ProcessResult.MessageLimitationFailed; + } + } + + user.Role.Limitations.ChatFromDate = today; + user.Role.Limitations.ChatGeneration = 20; + await repo.UpdateAsync(user); + + return ProcessResult.MessageLimitationSuccessfull; + } else { var today = DateTime.UtcNow; @@ -42,6 +69,7 @@ public async Task ProcessUserMessageLimitations(string userId) return ProcessResult.MessageLimitationSuccessfull; } + } public async Task ProcessUserRecipeLimitations(string userId) @@ -49,31 +77,23 @@ public async Task ProcessUserRecipeLimitations(string userId) var user = await repo.GetFirstOrDefaultAsync(a => a.UserId == userId); ArgumentNullException.ThrowIfNull(user, nameof(user)); - if (user.Role.Type == RoleType.Free) + if (user.Role.Limitations.RecipeGeneration <= 0) { return ProcessResult.RecipeLimitationFailed; } - else if (user.Role.Type == RoleType.Premium) + else if (user.Role.Limitations.RecipeGeneration > 0) { - if (user.Role.Limitations.RecipeGeneration > 0) - { - user.Role.Limitations.RecipeGeneration--; - await repo.UpdateAsync(user); - return ProcessResult.RecipeLimitationSuccessfull; - } - else - { - return ProcessResult.RecipeLimitationFailed; - } + user.Role.Limitations.RecipeGeneration--; + await repo.UpdateAsync(user); + + return ProcessResult.RecipeLimitationSuccessfull; } else if (user.Role.Type == RoleType.Admin) { return ProcessResult.RecipeLimitationSuccessfull; } - else - { - return ProcessResult.RecipeLimitationFailed; - } + + return ProcessResult.RecipeLimitationFailed; } public async Task ProcessAdminLimitations(string userId) From a8dc9bb6869c82b815ef3169dd391352f563dc73 Mon Sep 17 00:00:00 2001 From: David Petkov Date: Thu, 8 Aug 2024 18:40:14 +0300 Subject: [PATCH 05/45] Gift tokens --- .../CookingAppReact/src/pages/admin/Admin.jsx | 5 +++++ .../CookingApp/Controllers/AdminController.cs | 17 ++++++++++++++++- .../UserProfile/IUserProfileService.cs | 3 ++- .../Services/UserProfile/UserProfileService.cs | 18 ++++++++++++++++-- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/client/CookingAppReact/src/pages/admin/Admin.jsx b/src/client/CookingAppReact/src/pages/admin/Admin.jsx index 8d8c73b8..6a283a9a 100644 --- a/src/client/CookingAppReact/src/pages/admin/Admin.jsx +++ b/src/client/CookingAppReact/src/pages/admin/Admin.jsx @@ -128,6 +128,11 @@ export default function Admin() {
+
+

{"Gift 100 chat tokens & 10 recipe tokens"}

+ + +
); } \ No newline at end of file diff --git a/src/server/CookingApp/Controllers/AdminController.cs b/src/server/CookingApp/Controllers/AdminController.cs index 6e3eaced..459d5ffa 100644 --- a/src/server/CookingApp/Controllers/AdminController.cs +++ b/src/server/CookingApp/Controllers/AdminController.cs @@ -4,6 +4,7 @@ using CookingApp.Models.Enums; using CookingApp.Services.Limitation; using CookingApp.Services.Stripe; + using CookingApp.Services.UserProfile; using CookingApp.ViewModels.Api; using CookingApp.ViewModels.Stripe.Customer; using CookingApp.ViewModels.Stripe.Statistics; @@ -11,7 +12,10 @@ using Microsoft.AspNetCore.Mvc; [ApiController] - public class AdminController(IStripeService stripeService, ILimitationService limitationService, IHttpContextAccessor contextAccessor) : ControllerBase + public class AdminController(IStripeService stripeService, + ILimitationService limitationService, + IUserProfileService userProfileService, + IHttpContextAccessor contextAccessor) : ControllerBase { [HttpGet("subscribers-count")] public async Task GetSubsCount() @@ -103,5 +107,16 @@ public async Task GetIncome30DaysBack() Status = 403 }; } + + [HttpPost("gift-tokens/{userId}")] + public async Task GiftTokensByUid(string userId) + { + var limitResult = await limitationService.ProcessAdminLimitations(GetUser.ProfileId(contextAccessor)); + + if (limitResult == ProcessResult.LimitationSuccessfull) + { + await userProfileService.GiftTokens(userId); + } + } } } diff --git a/src/server/CookingApp/Services/UserProfile/IUserProfileService.cs b/src/server/CookingApp/Services/UserProfile/IUserProfileService.cs index bfa99b21..e6e1118a 100644 --- a/src/server/CookingApp/Services/UserProfile/IUserProfileService.cs +++ b/src/server/CookingApp/Services/UserProfile/IUserProfileService.cs @@ -8,6 +8,7 @@ public interface IUserProfileService Task ConfigurePreferences(ConfigurePreferencesRequest configureProfileRequest); - Task SaveInterfacePreferences (PreferencesRequest preferencesRequest); + Task SaveInterfacePreferences(PreferencesRequest preferencesRequest); + Task GiftTokens(string userId); } } diff --git a/src/server/CookingApp/Services/UserProfile/UserProfileService.cs b/src/server/CookingApp/Services/UserProfile/UserProfileService.cs index dd877c32..8a5e12e8 100644 --- a/src/server/CookingApp/Services/UserProfile/UserProfileService.cs +++ b/src/server/CookingApp/Services/UserProfile/UserProfileService.cs @@ -49,8 +49,6 @@ public async Task ConfigurePreferences(ConfigurePreferencesRequest configureProf throw new NotFoundException(); } - - profile.Allergies = configureProfileRequest.Allergies; profile.AvoidedFoods = configureProfileRequest.AvoidedFoods; profile.DietaryPreference = configureProfileRequest.DietaryPreference; @@ -76,5 +74,21 @@ public async Task SaveInterfacePreferences(PreferencesRequest preferencesRequest await profileRepo.UpdateAsync(profile); } + + public async Task GiftTokens(string userId) + { + var profile = await profileRepo + .GetFirstOrDefaultAsync(a => a.UserId == userId); + + if (profile is null) + { + throw new NotFoundException(); + } + + profile.Role.Limitations.RecipeGeneration = profile.Role.Limitations.RecipeGeneration + 10; + profile.Role.Limitations.RecipeGeneration = profile.Role.Limitations.ChatGeneration + 100; + + await profileRepo.UpdateAsync(profile); + } } } From 283d47cbacd5aedc20ba3187bb052b7c56d11e05 Mon Sep 17 00:00:00 2001 From: David Petkov Date: Thu, 8 Aug 2024 18:52:46 +0300 Subject: [PATCH 06/45] Copy to clipboard --- src/client/CookingAppReact/src/pages/settings/Settings.jsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/client/CookingAppReact/src/pages/settings/Settings.jsx b/src/client/CookingAppReact/src/pages/settings/Settings.jsx index 5d6be220..ef8a5b1e 100644 --- a/src/client/CookingAppReact/src/pages/settings/Settings.jsx +++ b/src/client/CookingAppReact/src/pages/settings/Settings.jsx @@ -3,11 +3,16 @@ import { UserIcon } from "@heroicons/react/24/outline"; import { ClipboardDocumentIcon } from "@heroicons/react/24/outline"; import { useState } from "react"; import { useSelector } from "react-redux"; +import { getLoggedInUser } from "@/msal/userHelper"; export default function Settings() { const isOpenRecipes = useSelector((state) => state.ui.recipesOpen); const isOpenSideBar = useSelector((state) => state.ui.sidebarOpen); + const handleCopy = async () => { + await navigator.clipboard.writeText(getLoggedInUser().idTokenClaims.sub); + }; + return (

- Copy User Id +

From e90b7c765884f3c248bb0853792a9884c54c49d7 Mon Sep 17 00:00:00 2001 From: Ekrem Beytula Date: Fri, 9 Aug 2024 09:26:50 +0300 Subject: [PATCH 07/45] Misspell --- .../src/components/sidebar/Sidebar.jsx | 12 +++++++--- .../{Subscribtion.jsx => Subscription.jsx} | 22 +++++++++++++------ 2 files changed, 24 insertions(+), 10 deletions(-) rename src/client/CookingAppReact/src/pages/subscribtion/{Subscribtion.jsx => Subscription.jsx} (86%) diff --git a/src/client/CookingAppReact/src/components/sidebar/Sidebar.jsx b/src/client/CookingAppReact/src/components/sidebar/Sidebar.jsx index 5979550e..6d4f517c 100644 --- a/src/client/CookingAppReact/src/components/sidebar/Sidebar.jsx +++ b/src/client/CookingAppReact/src/components/sidebar/Sidebar.jsx @@ -116,16 +116,22 @@ export default function Sidebar() { onClick={handleNewChat} /> - - )} From a1490bfbf878bf78f927574e4905403e7f965b5a Mon Sep 17 00:00:00 2001 From: Ekrem Beytula Date: Fri, 9 Aug 2024 09:56:19 +0300 Subject: [PATCH 11/45] Logic fix I was giving 110 recipes and 0 chats --- .../CookingApp/Services/UserProfile/UserProfileService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/CookingApp/Services/UserProfile/UserProfileService.cs b/src/server/CookingApp/Services/UserProfile/UserProfileService.cs index 8a5e12e8..cfe3fead 100644 --- a/src/server/CookingApp/Services/UserProfile/UserProfileService.cs +++ b/src/server/CookingApp/Services/UserProfile/UserProfileService.cs @@ -86,7 +86,7 @@ public async Task GiftTokens(string userId) } profile.Role.Limitations.RecipeGeneration = profile.Role.Limitations.RecipeGeneration + 10; - profile.Role.Limitations.RecipeGeneration = profile.Role.Limitations.ChatGeneration + 100; + profile.Role.Limitations.ChatGeneration = profile.Role.Limitations.ChatGeneration + 100; await profileRepo.UpdateAsync(profile); } From 9ed591145546f14429cc9cec4fb8d87f9690a763 Mon Sep 17 00:00:00 2001 From: Ekrem Beytula Date: Fri, 9 Aug 2024 09:56:26 +0300 Subject: [PATCH 12/45] Update AdminController.cs --- src/server/CookingApp/Controllers/AdminController.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/server/CookingApp/Controllers/AdminController.cs b/src/server/CookingApp/Controllers/AdminController.cs index 459d5ffa..5b34f06d 100644 --- a/src/server/CookingApp/Controllers/AdminController.cs +++ b/src/server/CookingApp/Controllers/AdminController.cs @@ -109,7 +109,7 @@ public async Task GetIncome30DaysBack() } [HttpPost("gift-tokens/{userId}")] - public async Task GiftTokensByUid(string userId) + public async Task GiftTokensByUid(string userId) { var limitResult = await limitationService.ProcessAdminLimitations(GetUser.ProfileId(contextAccessor)); @@ -117,6 +117,10 @@ public async Task GiftTokensByUid(string userId) { await userProfileService.GiftTokens(userId); } + return new ApiResponse() + { + Status = 200 + }; } } } From 822a1e5f1e72c08de3ed67754dfc94bd2c3d1260 Mon Sep 17 00:00:00 2001 From: Ekrem Beytula Date: Fri, 9 Aug 2024 09:57:24 +0300 Subject: [PATCH 13/45] Client side functionality --- .../CookingAppReact/src/hooks/useGift.js | 17 ++ src/client/CookingAppReact/src/http/admin.js | 12 + .../CookingAppReact/src/pages/admin/Admin.jsx | 267 ++++++++++-------- 3 files changed, 174 insertions(+), 122 deletions(-) create mode 100644 src/client/CookingAppReact/src/hooks/useGift.js create mode 100644 src/client/CookingAppReact/src/http/admin.js diff --git a/src/client/CookingAppReact/src/hooks/useGift.js b/src/client/CookingAppReact/src/hooks/useGift.js new file mode 100644 index 00000000..a35b42ac --- /dev/null +++ b/src/client/CookingAppReact/src/hooks/useGift.js @@ -0,0 +1,17 @@ +import { useMutation } from "@tanstack/react-query"; +import { gift } from "@/http/admin"; +import toast from "react-hot-toast"; + +export default function useGift() { + const { mutate, isPending, isError, error } = useMutation({ + mutationFn: gift, + onError: () => { + toast.error("There was an error!"); + }, + onSuccess: async () => { + toast.success("Successfully gifted!"); + }, + }); + + return { mutate, isPending, isError, error }; +} diff --git a/src/client/CookingAppReact/src/http/admin.js b/src/client/CookingAppReact/src/http/admin.js new file mode 100644 index 00000000..575d7491 --- /dev/null +++ b/src/client/CookingAppReact/src/http/admin.js @@ -0,0 +1,12 @@ +const ip = import.meta.env.VITE_PUBLIC_PERSONAL_IP; + +export async function gift({ token, userId }) { + const response = await fetch(`${ip}/gift-tokens/${userId}`, { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }); + return response; +} diff --git a/src/client/CookingAppReact/src/pages/admin/Admin.jsx b/src/client/CookingAppReact/src/pages/admin/Admin.jsx index 6a283a9a..87db9273 100644 --- a/src/client/CookingAppReact/src/pages/admin/Admin.jsx +++ b/src/client/CookingAppReact/src/pages/admin/Admin.jsx @@ -1,138 +1,161 @@ import { - Card, - CardBody, - CardHeader, - Typography, - } from "@material-tailwind/react"; - import Chart from "react-apexcharts"; - import { CurrencyEuroIcon } from "@heroicons/react/24/outline"; - - const chartConfig = { - type: "line", - height: 240, - series: [ - { - name: "Sales", - data: [50, 40, 300, 320, 500, 350, 200, 230, 500], - }, - ], - options: { - chart: { - toolbar: { - show: false, - }, - }, - title: { - show: "", - }, - dataLabels: { - enabled: false, + Card, + CardBody, + CardHeader, + Typography, +} from "@material-tailwind/react"; +import Chart from "react-apexcharts"; +import { CurrencyEuroIcon } from "@heroicons/react/24/outline"; +import { useRef } from "react"; +import useGift from "@/hooks/useGift"; +import { getToken } from "@/msal/msal"; +const chartConfig = { + type: "line", + height: 240, + series: [ + { + name: "Sales", + data: [50, 40, 300, 320, 500, 350, 200, 230, 500], + }, + ], + options: { + chart: { + toolbar: { + show: false, }, - colors: ["#020617"], - stroke: { - lineCap: "round", - curve: "smooth", + }, + title: { + show: "", + }, + dataLabels: { + enabled: false, + }, + colors: ["#020617"], + stroke: { + lineCap: "round", + curve: "smooth", + }, + markers: { + size: 1, + }, + xaxis: { + axisTicks: { + show: false, }, - markers: { - size: 1, + axisBorder: { + show: false, }, - xaxis: { - axisTicks: { - show: false, - }, - axisBorder: { - show: false, - }, - labels: { - style: { - colors: "#616161", - fontSize: "12px", - fontFamily: "inherit", - fontWeight: 400, - }, + labels: { + style: { + colors: "#616161", + fontSize: "12px", + fontFamily: "inherit", + fontWeight: 400, }, - categories: [ - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - ], }, - yaxis: { - labels: { - style: { - colors: "#616161", - fontSize: "12px", - fontFamily: "inherit", - fontWeight: 400, - }, + categories: [ + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ], + }, + yaxis: { + labels: { + style: { + colors: "#616161", + fontSize: "12px", + fontFamily: "inherit", + fontWeight: 400, }, }, - grid: { - show: true, - borderColor: "#dddddd", - strokeDashArray: 5, - xaxis: { - lines: { - show: true, - }, - }, - padding: { - top: 5, - right: 20, + }, + grid: { + show: true, + borderColor: "#dddddd", + strokeDashArray: 5, + xaxis: { + lines: { + show: true, }, }, - fill: { - opacity: 0.8, - }, - tooltip: { - theme: "dark", + padding: { + top: 5, + right: 20, }, }, - }; + fill: { + opacity: 0.8, + }, + tooltip: { + theme: "dark", + }, + }, +}; export default function Admin() { - return ( - <> -
- - -
- -
-
- - Sales this week - - - For the period mon - sun - -
-
- - - -
+ const ref = useRef(); + const { mutate } = useGift(); + async function handleGifting() { + if (ref.current.value && ref.current.value !== "") { + const token = await getToken(); + mutate({ token: token, userId: ref.current.value }); + ref.current.value = ""; + } + } + return ( + <> +
+ + +
+
-
-

{"Gift 100 chat tokens & 10 recipe tokens"}

- - +
+ + Sales this week + + + For the period mon - sun +
- - ); - } \ No newline at end of file + + + + + +
+
+

+ {"Gift 100 chat tokens & 10 recipe tokens"} +

+ + +
+ + ); +} From 9c70b72cd11b5ed1598a589be0c5be535bc92591 Mon Sep 17 00:00:00 2001 From: Ekrem Beytula Date: Fri, 9 Aug 2024 10:10:28 +0300 Subject: [PATCH 14/45] Sidebar fixed --- .../CookingAppReact/src/components/sidebar/Sidebar.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/CookingAppReact/src/components/sidebar/Sidebar.jsx b/src/client/CookingAppReact/src/components/sidebar/Sidebar.jsx index 6d4f517c..6a10a998 100644 --- a/src/client/CookingAppReact/src/components/sidebar/Sidebar.jsx +++ b/src/client/CookingAppReact/src/components/sidebar/Sidebar.jsx @@ -117,7 +117,7 @@ export default function Sidebar() { /> +
From b047c8067aae09e94de0d609fdfc4be5d3591b15 Mon Sep 17 00:00:00 2001 From: Ekrem Beytula Date: Fri, 9 Aug 2024 12:21:29 +0300 Subject: [PATCH 18/45] Limitation messages --- src/server/CookingApp/Controllers/ChatController.cs | 5 +++-- src/server/CookingApp/Controllers/RecipeController.cs | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/server/CookingApp/Controllers/ChatController.cs b/src/server/CookingApp/Controllers/ChatController.cs index 7f2ee55a..1d6ab371 100644 --- a/src/server/CookingApp/Controllers/ChatController.cs +++ b/src/server/CookingApp/Controllers/ChatController.cs @@ -13,6 +13,7 @@ namespace CookingApp.Controllers using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System.ComponentModel.DataAnnotations; + using CookingApp.Common; [ApiController] public class ChatController(IChatService chatService, @@ -36,10 +37,10 @@ public async Task SendMessage([FromBody] MessageData message) }; } - return new ApiResponse() + return new ApiResponse() { Status = 403, - Data = limitationResult + Data = LimitationMessages.ProcessMessages[limitationResult] }; } diff --git a/src/server/CookingApp/Controllers/RecipeController.cs b/src/server/CookingApp/Controllers/RecipeController.cs index e3a7596c..16729fb2 100644 --- a/src/server/CookingApp/Controllers/RecipeController.cs +++ b/src/server/CookingApp/Controllers/RecipeController.cs @@ -8,6 +8,7 @@ using System.ComponentModel.DataAnnotations; using CookingApp.Infrastructure.Extensions; using CookingApp.ViewModels.Recipes; +using CookingApp.Common; namespace CookingApp.Controllers { @@ -32,10 +33,10 @@ public async Task CreateRecipe([FromBody] string request) }; } - return new ApiResponse() + return new ApiResponse() { Status = 403, - Data = limitationResult + Data = LimitationMessages.ProcessMessages[limitationResult] }; } From 4118554d6bcedf04f9c0e45e7591d5cab5f6b389 Mon Sep 17 00:00:00 2001 From: Ekrem Beytula Date: Fri, 9 Aug 2024 12:51:04 +0300 Subject: [PATCH 19/45] . --- .gitignore | 1 - .../CookingApp/Common/LimitationMessages.cs | 17 +++++++++++++++++ .../ViewModels/Recipes/RecipeCard.cs | 19 +++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 src/server/CookingApp/Common/LimitationMessages.cs create mode 100644 src/server/CookingApp/ViewModels/Recipes/RecipeCard.cs diff --git a/.gitignore b/.gitignore index 5e4b43eb..33fcb427 100644 --- a/.gitignore +++ b/.gitignore @@ -405,4 +405,3 @@ src/server/CookingApp/Properties/launchSettings.json .prettierrc .prettierignore src/server/CookingApp/Properties/launchSettings.json -/src/server/CookingApp diff --git a/src/server/CookingApp/Common/LimitationMessages.cs b/src/server/CookingApp/Common/LimitationMessages.cs new file mode 100644 index 00000000..d69bd1d3 --- /dev/null +++ b/src/server/CookingApp/Common/LimitationMessages.cs @@ -0,0 +1,17 @@ +using CookingApp.Models.Enums; + +namespace CookingApp.Common +{ + public static class LimitationMessages + { + public static readonly Dictionary ProcessMessages =new Dictionary() + { + { + ProcessResult.MessageLimitationFailed,"You have reached the maximum number of messages!" + }, + { + ProcessResult.RecipeLimitationFailed,"You have reached the maximum number of recipe generations!" + }, + }; + } +} diff --git a/src/server/CookingApp/ViewModels/Recipes/RecipeCard.cs b/src/server/CookingApp/ViewModels/Recipes/RecipeCard.cs new file mode 100644 index 00000000..e5e9921e --- /dev/null +++ b/src/server/CookingApp/ViewModels/Recipes/RecipeCard.cs @@ -0,0 +1,19 @@ +using CookingApp.Models.Entities; +using CookingApp.Models.Enums; +using Recipe = CookingApp.Models.Entities.Recipe; + +namespace CookingApp.ViewModels.Recipes +{ + public class RecipeCard + { + + public string UserId { get; set; } = default!; + public string Title { get; set; } = default!; + public bool IsArchived { get; set; } + public string Description { get; set; } = default!; + + public string Duration { get; set; } = default!; + public string ImageUrl { get; set; } = default!; + public int NumberOfPortions { get; set; } + } +} From b0b54b218c455905f648c154975c87756fde0b45 Mon Sep 17 00:00:00 2001 From: David Hristov Date: Fri, 9 Aug 2024 20:15:30 +0300 Subject: [PATCH 20/45] ignore launch --- .gitignore | 2 ++ .../CookingApp/Properties/launchSettings.json | 36 ------------------- 2 files changed, 2 insertions(+), 36 deletions(-) delete mode 100644 src/server/CookingApp/Properties/launchSettings.json diff --git a/.gitignore b/.gitignore index 33fcb427..e6651368 100644 --- a/.gitignore +++ b/.gitignore @@ -405,3 +405,5 @@ src/server/CookingApp/Properties/launchSettings.json .prettierrc .prettierignore src/server/CookingApp/Properties/launchSettings.json +/src/server/CookingApp/appsettings.Development.json +/src/server/CookingApp/Properties/launchSettings.json diff --git a/src/server/CookingApp/Properties/launchSettings.json b/src/server/CookingApp/Properties/launchSettings.json deleted file mode 100644 index 36314d81..00000000 --- a/src/server/CookingApp/Properties/launchSettings.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "profiles": { - "http": { - "commandName": "Project", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "dotnetRunMessages": true, - "applicationUrl": "http://192.168.0.105:8000" - }, - "https": { - "commandName": "Project", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "dotnetRunMessages": true, - "applicationUrl": "https://localhost:8001;http://localhost:8000" - }, - "Container (Dockerfile)": { - "commandName": "Docker", - "launchBrowser": true, - "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", - "environmentVariables": { - "ASPNETCORE_HTTPS_PORTS": "8001", - "ASPNETCORE_HTTP_PORTS": "8000" - }, - "publishAllPorts": true, - "useSSL": true - } - }, - "$schema": "http://json.schemastore.org/launchsettings.json" -} From c028ec01a01f0c1e0e2f1d83f786a25f7ff1cb68 Mon Sep 17 00:00:00 2001 From: David Hristov Date: Fri, 9 Aug 2024 20:48:10 +0300 Subject: [PATCH 21/45] unfinished my sub endpoint --- .../Controllers/StripeController.cs | 14 ++++++++++++++ .../Services/Stripe/IStripeService.cs | 2 ++ .../Services/Stripe/StripeService.cs | 19 +++++++++++++++++++ .../Stripe/Customer/CustomerData.cs | 4 +++- 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/server/CookingApp/Controllers/StripeController.cs b/src/server/CookingApp/Controllers/StripeController.cs index 07f17668..827b782f 100644 --- a/src/server/CookingApp/Controllers/StripeController.cs +++ b/src/server/CookingApp/Controllers/StripeController.cs @@ -1,4 +1,6 @@  +using CookingApp.ViewModels.Stripe.Customer; + namespace CookingApp.Controllers { using CookingApp.Services.Stripe; @@ -23,6 +25,18 @@ public async Task>> GetProductsAsync() } + [HttpGet("my-subscription")] + public async Task> GetMySubscription() + { + var sub = await stripeService.GetSubscription(); + return new ApiResponse() + { + Status = 200, + Data = sub + }; + } + + [HttpPost("subscription")] public async Task> CreateSubscriptionAsync([FromBody] SubscriptionCreation model) { diff --git a/src/server/CookingApp/Services/Stripe/IStripeService.cs b/src/server/CookingApp/Services/Stripe/IStripeService.cs index 5b754e87..97f3f37b 100644 --- a/src/server/CookingApp/Services/Stripe/IStripeService.cs +++ b/src/server/CookingApp/Services/Stripe/IStripeService.cs @@ -14,5 +14,7 @@ public interface IStripeService Task> GetAllSubs(); Task GetSubsStats(); Task GetIncome30DaysBack(); + + Task GetSubscription(); } } diff --git a/src/server/CookingApp/Services/Stripe/StripeService.cs b/src/server/CookingApp/Services/Stripe/StripeService.cs index f1515a23..94d39356 100644 --- a/src/server/CookingApp/Services/Stripe/StripeService.cs +++ b/src/server/CookingApp/Services/Stripe/StripeService.cs @@ -46,6 +46,8 @@ public async Task> GetProductsAsync() return result; } + + /// /// Creates a subscription with a status "default_incomplete" because the subscription /// requires a payment. It automatically generates an an initial Invoice. @@ -158,6 +160,23 @@ public async Task> GetAllSubs() return allActiveUsers; } + public async Task GetSubscription() + { + var user = await GetUser.Profile(httpContextAccessor, userRepo); + var stripeCustomer = await customerService.GetAsync(user.StripeId); + var customer = mapper.Map(stripeCustomer); + + var subscriptionListOptions = new SubscriptionListOptions + { + Customer = customer.Id, + }; + + var subscriptions = await subscriptionService.ListAsync(subscriptionListOptions); + customer.Subscriptions = mapper.Map>(subscriptions.Data); + + return customer; + } + public async Task GetSubsStats() { var subStats = new SubscriptionStatistics(); diff --git a/src/server/CookingApp/ViewModels/Stripe/Customer/CustomerData.cs b/src/server/CookingApp/ViewModels/Stripe/Customer/CustomerData.cs index 51e9d2e5..7ed94d43 100644 --- a/src/server/CookingApp/ViewModels/Stripe/Customer/CustomerData.cs +++ b/src/server/CookingApp/ViewModels/Stripe/Customer/CustomerData.cs @@ -16,11 +16,13 @@ public class CustomerData : IMapFrom public List Subscriptions { get; set; } = new List(); } - public class SubscriptionState + public class SubscriptionState : IMapFrom { public string Id { get; set; } = string.Empty; public string PriceId { get; set; } = string.Empty; public string State { get; set; } = string.Empty; public DateTime CancelAt { get; set; } + public DateTime Created { get; set; } + public DateTime CurrentPeriodEnd { get; set; } } } \ No newline at end of file From b2859a218b298df64d2e6e6a53b22a4b102e64d9 Mon Sep 17 00:00:00 2001 From: Ekrem Beytula Date: Sat, 10 Aug 2024 10:49:52 +0300 Subject: [PATCH 22/45] cancelling subs on the client demo --- .../CookingAppReact/src/hooks/useCancelSub.js | 18 ++++ .../src/hooks/useMySubscription.js | 20 +++++ src/client/CookingAppReact/src/http/subs.js | 28 ++++++ .../subscribtion/SubscribtionDetails.jsx | 90 ++++++++++++------- .../Controllers/StripeController.cs | 6 +- .../Attributes/DateTimeFormatAttribute.cs | 12 +++ .../Services/Stripe/IStripeService.cs | 2 +- .../Services/Stripe/StripeService.cs | 8 +- .../Stripe/Customer/CustomerData.cs | 2 +- 9 files changed, 146 insertions(+), 40 deletions(-) create mode 100644 src/client/CookingAppReact/src/hooks/useCancelSub.js create mode 100644 src/client/CookingAppReact/src/hooks/useMySubscription.js create mode 100644 src/server/CookingApp/Infrastructure/Attributes/DateTimeFormatAttribute.cs diff --git a/src/client/CookingAppReact/src/hooks/useCancelSub.js b/src/client/CookingAppReact/src/hooks/useCancelSub.js new file mode 100644 index 00000000..49acf74c --- /dev/null +++ b/src/client/CookingAppReact/src/hooks/useCancelSub.js @@ -0,0 +1,18 @@ +import { useMutation } from "@tanstack/react-query"; +import toast from "react-hot-toast"; +import { cancelSub } from "@/http/subs"; + +export default function useCancelSub({ refetchFn }) { + const { mutate, isPending, isError, error } = useMutation({ + mutationFn: cancelSub, + onError: () => { + toast.error("There was an error!"); + }, + onSuccess: async () => { + toast.success("Successfully cancelled!"); + refetchFn(); + }, + }); + + return { mutate, isPending, isError, error }; +} diff --git a/src/client/CookingAppReact/src/hooks/useMySubscription.js b/src/client/CookingAppReact/src/hooks/useMySubscription.js new file mode 100644 index 00000000..3c35d0e8 --- /dev/null +++ b/src/client/CookingAppReact/src/hooks/useMySubscription.js @@ -0,0 +1,20 @@ +import { useQuery } from "@tanstack/react-query"; +import { getRecipeById } from "@/http/recipe"; +import { getToken } from "@/msal/msal"; +import { mySub } from "@/http/subs"; +const useMySubscription = () => { + const { data, isPending, isError, error, refetch } = useQuery({ + queryKey: ["mySub"], + queryFn: async () => { + const token = await getToken(); + const userSub = await mySub({ + token: token, + }); + return userSub; + }, + }); + + return { data, isPending, isError, error, refetch }; +}; + +export default useMySubscription; diff --git a/src/client/CookingAppReact/src/http/subs.js b/src/client/CookingAppReact/src/http/subs.js index da986a00..0faaf994 100644 --- a/src/client/CookingAppReact/src/http/subs.js +++ b/src/client/CookingAppReact/src/http/subs.js @@ -33,3 +33,31 @@ export async function createSub({ token, email, priceId }) { console.log(data); return data; } +export async function mySub({ token }) { + const response = await fetch(`${ip}/api/stripe/my-subscription`, { + method: "GET", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }); + if (!response.ok) { + throw new Error(response.errors); + } + const data = await response.json(); + return data.data; +} +export async function cancelSub({ token, subscriptionId }) { + const response = await fetch(`${ip}/api/stripe/cancel/${subscriptionId}`, { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }); + if (!response.ok) { + throw new Error(response.errors); + } + const data = await response.json(); + return data.data; +} diff --git a/src/client/CookingAppReact/src/pages/subscribtion/SubscribtionDetails.jsx b/src/client/CookingAppReact/src/pages/subscribtion/SubscribtionDetails.jsx index 23d3b2ea..8e911d20 100644 --- a/src/client/CookingAppReact/src/pages/subscribtion/SubscribtionDetails.jsx +++ b/src/client/CookingAppReact/src/pages/subscribtion/SubscribtionDetails.jsx @@ -1,48 +1,76 @@ import { useSelector } from "react-redux"; import stripe from "../../assets/by-stripe.png"; import { FireIcon } from "@heroicons/react/24/outline"; +import useMySubscription from "@/hooks/useMySubscription"; +import useCancelSub from "@/hooks/useCancelSub"; +import { getToken } from "@/msal/msal"; export default function SubscribtionDetails() { const isOpenRecipes = useSelector((state) => state.ui.recipesOpen); const isOpenSideBar = useSelector((state) => state.ui.sidebarOpen); + const { data, isPending, refetch } = useMySubscription(); + const { mutate } = useCancelSub({ refetchFn: refetch }); + console.log(data); + async function handleCancellation() { + const token = await getToken(); + mutate({ token: token, subscriptionId: data.subscriptions[0].id }); + } return (
-
-
-
    -
  • - Your subscription plan: -
  • -
  • -
    +
    + {data && !isPending && ( + <> +
      +
    • Your subscription plan:
    • + +
    • +
      - Premium -
      -
    • -
    • • Unlimited messages
    • -
    • • Unlimited chats
    • -
    • - • More than enough recipies -
    • -
    • - • Customizable dietary options -
    • -
    • • Free cancellation
    • -
    -
  • +
  • + {`Created at ${data.subscriptions[0].created}`} +
  • + + {data.subscriptions[0].cancelAt ? ( +
  • + {`Your subscription has been cancelled and it will expire on ${data.subscriptions[0].cancelAt}`} +
  • + ) : ( +
  • + {`Your next charge will be on ${data.subscriptions[0].currentPeriodEnd}`} +
  • + )} + +
  • + • Customizable dietary options +
  • +
  • + • Free cancellation +
  • +
+ +
+ stripe
- -
- stripe -
-
+ + )}
+
); } diff --git a/src/server/CookingApp/Controllers/StripeController.cs b/src/server/CookingApp/Controllers/StripeController.cs index 827b782f..bb64da79 100644 --- a/src/server/CookingApp/Controllers/StripeController.cs +++ b/src/server/CookingApp/Controllers/StripeController.cs @@ -49,10 +49,10 @@ public async Task> CreateSubscriptionA }; } - [HttpPost("cancel")] - public async Task> CancelSubscriptionAsync([FromBody] SubscriptionCancellation model) + [HttpPost("cancel/{subscriptionId}")] + public async Task> CancelSubscriptionAsync(string subscriptionId) { - var subscription = await stripeService.CancelSubscriptionAsync(model); + var subscription = await stripeService.CancelSubscriptionAsync(subscriptionId); return new ApiResponse() { diff --git a/src/server/CookingApp/Infrastructure/Attributes/DateTimeFormatAttribute.cs b/src/server/CookingApp/Infrastructure/Attributes/DateTimeFormatAttribute.cs new file mode 100644 index 00000000..a8067967 --- /dev/null +++ b/src/server/CookingApp/Infrastructure/Attributes/DateTimeFormatAttribute.cs @@ -0,0 +1,12 @@ +using System; + +[AttributeUsage(AttributeTargets.Property)] +sealed class DateTimeFormatAttribute : Attribute +{ + public string Format { get; } + + public DateTimeFormatAttribute(string format) + { + Format = format; + } +} diff --git a/src/server/CookingApp/Services/Stripe/IStripeService.cs b/src/server/CookingApp/Services/Stripe/IStripeService.cs index 97f3f37b..d443f779 100644 --- a/src/server/CookingApp/Services/Stripe/IStripeService.cs +++ b/src/server/CookingApp/Services/Stripe/IStripeService.cs @@ -10,7 +10,7 @@ public interface IStripeService { Task> GetProductsAsync(); Task CreateSubscriptionAsync(SubscriptionCreation model); - Task CancelSubscriptionAsync(SubscriptionCancellation model); + Task CancelSubscriptionAsync(string subscriptionId); Task> GetAllSubs(); Task GetSubsStats(); Task GetIncome30DaysBack(); diff --git a/src/server/CookingApp/Services/Stripe/StripeService.cs b/src/server/CookingApp/Services/Stripe/StripeService.cs index 94d39356..21fe81ce 100644 --- a/src/server/CookingApp/Services/Stripe/StripeService.cs +++ b/src/server/CookingApp/Services/Stripe/StripeService.cs @@ -126,15 +126,15 @@ public async Task CreateSubscriptionAsync(Subscrip /// /// Cancels a subscription immediatly. The customer will not be charged again for the subscription /// - public async Task CancelSubscriptionAsync(SubscriptionCancellation model) + public async Task CancelSubscriptionAsync(string subscriptionId) { - ArgumentException.ThrowIfNullOrEmpty(model.SubscriptionId); - var sub = await subscriptionService.GetAsync(model.SubscriptionId); + ArgumentException.ThrowIfNullOrEmpty(subscriptionId); + var sub = await subscriptionService.GetAsync(subscriptionId); var options = new SubscriptionUpdateOptions { CancelAtPeriodEnd=true }; - subscriptionService.Update(model.SubscriptionId, options); + subscriptionService.Update(subscriptionId, options); return new SubscriptionCancellationResponse(sub.CurrentPeriodEnd); diff --git a/src/server/CookingApp/ViewModels/Stripe/Customer/CustomerData.cs b/src/server/CookingApp/ViewModels/Stripe/Customer/CustomerData.cs index 7ed94d43..bbf1df45 100644 --- a/src/server/CookingApp/ViewModels/Stripe/Customer/CustomerData.cs +++ b/src/server/CookingApp/ViewModels/Stripe/Customer/CustomerData.cs @@ -21,7 +21,7 @@ public class SubscriptionState : IMapFrom public string Id { get; set; } = string.Empty; public string PriceId { get; set; } = string.Empty; public string State { get; set; } = string.Empty; - public DateTime CancelAt { get; set; } + public DateTime? CancelAt { get; set; } public DateTime Created { get; set; } public DateTime CurrentPeriodEnd { get; set; } } From 96fe31b5be52ac2e46de443c113ef7a69481b5d5 Mon Sep 17 00:00:00 2001 From: Ekrem Beytula Date: Sat, 10 Aug 2024 11:13:06 +0300 Subject: [PATCH 23/45] get the charging price --- .../src/pages/subscribtion/SubscribtionDetails.jsx | 6 +++--- .../CookingApp/Services/Stripe/StripeService.cs | 11 ++++++++++- .../ViewModels/Stripe/Customer/CustomerData.cs | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/client/CookingAppReact/src/pages/subscribtion/SubscribtionDetails.jsx b/src/client/CookingAppReact/src/pages/subscribtion/SubscribtionDetails.jsx index 8e911d20..ec633e4b 100644 --- a/src/client/CookingAppReact/src/pages/subscribtion/SubscribtionDetails.jsx +++ b/src/client/CookingAppReact/src/pages/subscribtion/SubscribtionDetails.jsx @@ -33,6 +33,9 @@ export default function SubscribtionDetails() { > Premium
+
  • + {data.subscriptions[0].price}€ a month +
  • {`Created at ${data.subscriptions[0].created}`} @@ -48,9 +51,6 @@ export default function SubscribtionDetails() {
  • )} -
  • - • Customizable dietary options -
  • • Free cancellation
  • diff --git a/src/server/CookingApp/Services/Stripe/StripeService.cs b/src/server/CookingApp/Services/Stripe/StripeService.cs index 21fe81ce..a424e9a0 100644 --- a/src/server/CookingApp/Services/Stripe/StripeService.cs +++ b/src/server/CookingApp/Services/Stripe/StripeService.cs @@ -38,7 +38,6 @@ public async Task> GetProductsAsync() foreach (var product in products) { - var price = await priceService.GetAsync(product.DefaultPriceId); result.Add(price.Id); } @@ -174,6 +173,16 @@ public async Task GetSubscription() var subscriptions = await subscriptionService.ListAsync(subscriptionListOptions); customer.Subscriptions = mapper.Map>(subscriptions.Data); + var sub = await subscriptionService.GetAsync(subscriptions.First().Id); + if (sub.Items != null && sub.Items.Data.Count > 0) + { + var subscriptionItem = sub.Items.Data.First(); + var priceId = subscriptionItem.Price.Id; + var price = await priceService.GetAsync(priceId); + customer.Subscriptions.First().Price = price.UnitAmountDecimal/100; + + } + return customer; } diff --git a/src/server/CookingApp/ViewModels/Stripe/Customer/CustomerData.cs b/src/server/CookingApp/ViewModels/Stripe/Customer/CustomerData.cs index bbf1df45..d02c6acf 100644 --- a/src/server/CookingApp/ViewModels/Stripe/Customer/CustomerData.cs +++ b/src/server/CookingApp/ViewModels/Stripe/Customer/CustomerData.cs @@ -19,7 +19,7 @@ public class CustomerData : IMapFrom public class SubscriptionState : IMapFrom { public string Id { get; set; } = string.Empty; - public string PriceId { get; set; } = string.Empty; + public decimal? Price { get; set; } public string State { get; set; } = string.Empty; public DateTime? CancelAt { get; set; } public DateTime Created { get; set; } From e62110b8cc641835c3a03f0078f6935b2169bd01 Mon Sep 17 00:00:00 2001 From: David Hristov Date: Sun, 11 Aug 2024 18:36:10 +0300 Subject: [PATCH 24/45] mistake warning added --- .../CookingAppReact/src/components/chat/ChatInput.jsx | 3 ++- src/client/CookingAppReact/src/store/uiSlice.js | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/client/CookingAppReact/src/components/chat/ChatInput.jsx b/src/client/CookingAppReact/src/components/chat/ChatInput.jsx index 67576cd0..5360df2d 100644 --- a/src/client/CookingAppReact/src/components/chat/ChatInput.jsx +++ b/src/client/CookingAppReact/src/components/chat/ChatInput.jsx @@ -106,7 +106,7 @@ export default function ChatInput() { }, [base64Image]); return ( -
    +
    +

    This AI may occasionally make mistakes. Please verify any important information.

    ); } diff --git a/src/client/CookingAppReact/src/store/uiSlice.js b/src/client/CookingAppReact/src/store/uiSlice.js index 0bab99d5..1d2e061a 100644 --- a/src/client/CookingAppReact/src/store/uiSlice.js +++ b/src/client/CookingAppReact/src/store/uiSlice.js @@ -37,9 +37,10 @@ const uiSlice = createSlice({ }, closeSidebar(state) { console.log(window.innerWidth); - if (window.innerWidth < 1300) { - state.sidebarOpen = false; - } + state.sidebarOpen = false; + // if (window.innerWidth < 1300) { + + // } }, toggleRecipes(state) { state.recipesOpen = !state.recipesOpen; From 21f3cb112487757122da04513cb6c217f9fe9513 Mon Sep 17 00:00:00 2001 From: David Hristov Date: Sun, 11 Aug 2024 18:37:39 +0300 Subject: [PATCH 25/45] warning responsive --- src/client/CookingAppReact/src/components/chat/ChatInput.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/CookingAppReact/src/components/chat/ChatInput.jsx b/src/client/CookingAppReact/src/components/chat/ChatInput.jsx index 5360df2d..8e97f957 100644 --- a/src/client/CookingAppReact/src/components/chat/ChatInput.jsx +++ b/src/client/CookingAppReact/src/components/chat/ChatInput.jsx @@ -140,7 +140,7 @@ export default function ChatInput() { /> -

    This AI may occasionally make mistakes. Please verify any important information.

    +

    This AI may occasionally make mistakes. Please verify any important information.

    ); } From eab2fbe6a1001dcaa8e1065404935893dee02044 Mon Sep 17 00:00:00 2001 From: David Hristov Date: Mon, 12 Aug 2024 11:52:39 +0300 Subject: [PATCH 26/45] dropdown works on recipes sidebar --- .../src/components/navbar/Navbar.jsx | 5 ++--- .../src/components/recipes/MyRecipes.jsx | 15 +++++++++++++-- src/client/CookingAppReact/src/store/uiSlice.js | 4 ++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/client/CookingAppReact/src/components/navbar/Navbar.jsx b/src/client/CookingAppReact/src/components/navbar/Navbar.jsx index d2d62ef7..953b3894 100644 --- a/src/client/CookingAppReact/src/components/navbar/Navbar.jsx +++ b/src/client/CookingAppReact/src/components/navbar/Navbar.jsx @@ -17,8 +17,7 @@ export default function Navbar() { const role = useSelector((state) => state.user.role.type); const sidebarOpen = useSelector((state) => state.ui.sidebarOpen); const recipesOpen = useSelector((state) => state.ui.recipesOpen); - const [dropDownOpen, setDropDownOpen] = useState(false); - + const dropDownOpen = useSelector((state) => state.ui.dropdownOpen) function handleSidebar() { dispatch(uiActions.openSidebar()); } @@ -31,7 +30,7 @@ export default function Navbar() { navigate("/"); } const toggleDropDown = () => { - setDropDownOpen(!dropDownOpen); + dispatch(uiActions.toggleDropdown()) }; return ( diff --git a/src/client/CookingAppReact/src/components/recipes/MyRecipes.jsx b/src/client/CookingAppReact/src/components/recipes/MyRecipes.jsx index 49ee6d85..1b2fd8a4 100644 --- a/src/client/CookingAppReact/src/components/recipes/MyRecipes.jsx +++ b/src/client/CookingAppReact/src/components/recipes/MyRecipes.jsx @@ -9,12 +9,16 @@ import { useEffect, useState, useRef } from "react"; import { Skeleton } from "../ui/skeleton"; import useFirstPageRecipes from "@/hooks/useFirstPageRecipes"; import "../../assets/css/animations.css"; +import UserMenu from "../userMenu/UserMenu"; + export default function MyRecipes() { const isOpen = useSelector((state) => state.ui.recipesOpen); const recipesState = useSelector((state) => state.ui.filteredRecipes); const [search, setSearch] = useState({ isTyping: false, message: "" }); - const timeoutRef = useRef(); + const timeoutRef = useRef(); + const dropDownOpen = useSelector((state) => state.ui.dropdownOpen) const dispatch = useDispatch(); + const { getFirstPageRecipes, loadMoreRecipes, @@ -24,6 +28,9 @@ export default function MyRecipes() { function handleRecipes() { dispatch(uiActions.toggleRecipes()); } + const toggleDropDown = () => { + dispatch(uiActions.toggleDropdown()) + }; useEffect(() => { if (isOpen) { console.log("open"); @@ -96,7 +103,11 @@ export default function MyRecipes() { onClick={handleRecipes} />

    My Recipes

    - + + +
    Date: Mon, 12 Aug 2024 12:16:01 +0300 Subject: [PATCH 27/45] dropdown modal close on outside click --- .../src/components/navbar/Navbar.jsx | 5 +- .../src/components/recipes/MyRecipes.jsx | 6 +- .../src/components/userMenu/UserMenu.jsx | 158 ++++++++++-------- 3 files changed, 101 insertions(+), 68 deletions(-) diff --git a/src/client/CookingAppReact/src/components/navbar/Navbar.jsx b/src/client/CookingAppReact/src/components/navbar/Navbar.jsx index 953b3894..34987ad8 100644 --- a/src/client/CookingAppReact/src/components/navbar/Navbar.jsx +++ b/src/client/CookingAppReact/src/components/navbar/Navbar.jsx @@ -98,7 +98,10 @@ export default function Navbar() {
    { + e.stopPropagation() + toggleDropDown() + }} />
    diff --git a/src/client/CookingAppReact/src/components/recipes/MyRecipes.jsx b/src/client/CookingAppReact/src/components/recipes/MyRecipes.jsx index 1b2fd8a4..b47d7857 100644 --- a/src/client/CookingAppReact/src/components/recipes/MyRecipes.jsx +++ b/src/client/CookingAppReact/src/components/recipes/MyRecipes.jsx @@ -104,7 +104,11 @@ export default function MyRecipes() { />

    My Recipes

    { + e.stopPropagation() + toggleDropDown() + } + } /> diff --git a/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx b/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx index 23bcecc1..989e68ab 100644 --- a/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx +++ b/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx @@ -1,77 +1,103 @@ -import "tailwindcss/tailwind.css"; -import React from "react"; +import React, { useEffect, useRef } from "react"; import { useNavigate } from "react-router-dom"; import { useSelector } from "react-redux"; -import { Cog6ToothIcon } from "@heroicons/react/24/outline"; -import { CreditCardIcon } from "@heroicons/react/24/outline"; -import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; +import { Cog6ToothIcon, CreditCardIcon, ExclamationTriangleIcon } from "@heroicons/react/24/outline"; import SignOutButton from "../auth/SignOutButton"; const UserMenu = ({ isOpen, toggleDropDown }) => { - const navigate = useNavigate(); - const isDarkTheme = useSelector(state => state.ui.isDarkTheme); + const navigate = useNavigate(); + const isDarkTheme = useSelector((state) => state.ui.isDarkTheme); + const menuRef = useRef(null); - if (!isOpen) return null; + useEffect(() => { + console.log(isOpen) + // Function to handle click outside the dropdown + function handleClickOutside(event) { + if (menuRef.current && !menuRef.current.contains(event.target)) { + console.log('kurac') + toggleDropDown(); // Close the dropdown only when clicking outside + } + } - return ( -
    event.stopPropagation()} - className={`absolute right-2 top-12 w-56 - ${isDarkTheme ? "bg-[#2F2F2F]" : "bg-white"} - border border-gray-300 rounded-3xl shadow-sm z-20`} - > -
    -
    -
    { navigate("/subscription"); toggleDropDown(); }} - title="Subscription" - > - - - Subscription - -
    -
    { navigate("/settings"); toggleDropDown(); }} - title="Settings" - > - - - Settings - -
    -
    { navigate("/rules-and-policies"); toggleDropDown(); }} - title="Settings" - > - - - Rules & Policies - -
    -
    -
    -
    - -
    -
    + // Attach the event listener to detect clicks outside + if (isOpen) { + // Attach the event listener after a slight delay to prevent immediate closing + document.addEventListener("click", handleClickOutside); + } + // Cleanup the event listener on component unmount + return () => { + document.removeEventListener("click", handleClickOutside); + }; + }, [toggleDropDown]); + + if (!isOpen) return null; + + return ( +
    e.stopPropagation()} // Attach the ref to the dropdown container + className={`absolute right-2 top-12 w-56 + ${isDarkTheme ? "bg-[#2F2F2F]" : "bg-white"} + border border-gray-300 rounded-3xl shadow-sm z-20`} + > +
    +
    +
    { + navigate("/subscription"); + }} + title="Subscription" + > + + + Subscription + +
    +
    { + navigate("/settings"); + }} + title="Settings" + > + + + Settings + +
    +
    { + navigate("/rules-and-policies"); + }} + title="Rules & Policies" + > + + + Rules & Policies + +
    +
    +
    +
    +
    - ); +
    +
    + ); }; export default UserMenu; From fee61fc1b2c1c24e6f7a7855c4dd32572c753bc3 Mon Sep 17 00:00:00 2001 From: David Hristov Date: Mon, 12 Aug 2024 12:17:17 +0300 Subject: [PATCH 28/45] remove unnecessary logs and comments --- .../src/components/userMenu/UserMenu.jsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx b/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx index 989e68ab..7530333f 100644 --- a/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx +++ b/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx @@ -11,20 +11,14 @@ const UserMenu = ({ isOpen, toggleDropDown }) => { useEffect(() => { console.log(isOpen) - // Function to handle click outside the dropdown function handleClickOutside(event) { if (menuRef.current && !menuRef.current.contains(event.target)) { - console.log('kurac') - toggleDropDown(); // Close the dropdown only when clicking outside + toggleDropDown(); } } - - // Attach the event listener to detect clicks outside if (isOpen) { - // Attach the event listener after a slight delay to prevent immediate closing document.addEventListener("click", handleClickOutside); } - // Cleanup the event listener on component unmount return () => { document.removeEventListener("click", handleClickOutside); }; @@ -35,7 +29,7 @@ const UserMenu = ({ isOpen, toggleDropDown }) => { return (
    e.stopPropagation()} // Attach the ref to the dropdown container + onClick={(e) => e.stopPropagation()} className={`absolute right-2 top-12 w-56 ${isDarkTheme ? "bg-[#2F2F2F]" : "bg-white"} border border-gray-300 rounded-3xl shadow-sm z-20`} From d6a607ccc2e27c869aa4c23ecba4c3b3ed056a6e Mon Sep 17 00:00:00 2001 From: Ekrem Beytula Date: Tue, 13 Aug 2024 09:33:06 +0300 Subject: [PATCH 29/45] Sidebar closing only on mobile --- .../src/components/navbar/Navbar.jsx | 11 +++++++---- .../src/components/sidebar/Sidebar.jsx | 17 +++++++++++++---- src/client/CookingAppReact/src/store/uiSlice.js | 9 +-------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/client/CookingAppReact/src/components/navbar/Navbar.jsx b/src/client/CookingAppReact/src/components/navbar/Navbar.jsx index 34987ad8..1551c573 100644 --- a/src/client/CookingAppReact/src/components/navbar/Navbar.jsx +++ b/src/client/CookingAppReact/src/components/navbar/Navbar.jsx @@ -17,8 +17,11 @@ export default function Navbar() { const role = useSelector((state) => state.user.role.type); const sidebarOpen = useSelector((state) => state.ui.sidebarOpen); const recipesOpen = useSelector((state) => state.ui.recipesOpen); - const dropDownOpen = useSelector((state) => state.ui.dropdownOpen) + const dropDownOpen = useSelector((state) => state.ui.dropdownOpen); function handleSidebar() { + if (recipesOpen && window.innerWidth < 1300) { + dispatch(uiActions.toggleRecipes()); + } dispatch(uiActions.openSidebar()); } function handleRecipes() { @@ -30,7 +33,7 @@ export default function Navbar() { navigate("/"); } const toggleDropDown = () => { - dispatch(uiActions.toggleDropdown()) + dispatch(uiActions.toggleDropdown()); }; return ( @@ -99,8 +102,8 @@ export default function Navbar() { { - e.stopPropagation() - toggleDropDown() + e.stopPropagation(); + toggleDropDown(); }} /> diff --git a/src/client/CookingAppReact/src/components/sidebar/Sidebar.jsx b/src/client/CookingAppReact/src/components/sidebar/Sidebar.jsx index 96debbd8..91aacf33 100644 --- a/src/client/CookingAppReact/src/components/sidebar/Sidebar.jsx +++ b/src/client/CookingAppReact/src/components/sidebar/Sidebar.jsx @@ -42,7 +42,10 @@ export default function Sidebar() { } }, [isOpen]); function handleChatSelection(chatId) { - dispatch(uiActions.closeSidebar()); + if (window.innerWidth < 1300) { + dispatch(uiActions.closeSidebar()); + } + navigate(`c/${chatId}`); } function handleNewChat() { @@ -67,17 +70,23 @@ export default function Sidebar() { dispatch(uiActions.closeSidebar()); } function handleClickDashboard() { - dispatch(uiActions.closeSidebar()); + if (window.innerWidth < 1300) { + dispatch(uiActions.closeSidebar()); + } navigate("/admin/dashboard"); } function handleClickSubscribtion() { - dispatch(uiActions.closeSidebar()); + if (window.innerWidth < 1300) { + dispatch(uiActions.closeSidebar()); + } navigate("/subscription"); } function handleClickYourSubscribtion() { - dispatch(uiActions.closeSidebar()); + if (window.innerWidth < 1300) { + dispatch(uiActions.closeSidebar()); + } navigate("/subscription/manage"); } diff --git a/src/client/CookingAppReact/src/store/uiSlice.js b/src/client/CookingAppReact/src/store/uiSlice.js index b4d8cd88..2c5a766b 100644 --- a/src/client/CookingAppReact/src/store/uiSlice.js +++ b/src/client/CookingAppReact/src/store/uiSlice.js @@ -29,19 +29,12 @@ const uiSlice = createSlice({ reducers: { openSidebar(state) { state.sidebarOpen = true; - if (window.innerWidth < 1300) { - state.recipesOpen = false; - } }, setIsShown(state) { state.isMessageWarningShowed = true; }, closeSidebar(state) { - console.log(window.innerWidth); state.sidebarOpen = false; - // if (window.innerWidth < 1300) { - - // } }, toggleRecipes(state) { state.recipesOpen = !state.recipesOpen; @@ -54,7 +47,7 @@ const uiSlice = createSlice({ state.recipesOpen = false; } }, - toggleDropdown(state){ + toggleDropdown(state) { state.dropdownOpen = !state.dropdownOpen; }, setInput(state, action) { From 819222af61a131acb8d3816a4009bf519a014f21 Mon Sep 17 00:00:00 2001 From: Ekrem Beytula Date: Tue, 13 Aug 2024 09:33:37 +0300 Subject: [PATCH 30/45] Update ChatInput.jsx --- src/client/CookingAppReact/src/components/chat/ChatInput.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/client/CookingAppReact/src/components/chat/ChatInput.jsx b/src/client/CookingAppReact/src/components/chat/ChatInput.jsx index 8e97f957..4cbf2573 100644 --- a/src/client/CookingAppReact/src/components/chat/ChatInput.jsx +++ b/src/client/CookingAppReact/src/components/chat/ChatInput.jsx @@ -140,7 +140,10 @@ export default function ChatInput() { /> -

    This AI may occasionally make mistakes. Please verify any important information.

    +

    + This AI may occasionally make mistakes. Please verify any important + information. +

    ); } From 2d13b0d99d1c17c82774fc9c8de684f44d6cc53f Mon Sep 17 00:00:00 2001 From: Ekrem Beytula Date: Tue, 13 Aug 2024 09:35:28 +0300 Subject: [PATCH 31/45] Update UserMenu.jsx --- .../src/components/userMenu/UserMenu.jsx | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx b/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx index 7530333f..c84a13bf 100644 --- a/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx +++ b/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx @@ -1,7 +1,11 @@ import React, { useEffect, useRef } from "react"; import { useNavigate } from "react-router-dom"; import { useSelector } from "react-redux"; -import { Cog6ToothIcon, CreditCardIcon, ExclamationTriangleIcon } from "@heroicons/react/24/outline"; +import { + Cog6ToothIcon, + CreditCardIcon, + ExclamationTriangleIcon, +} from "@heroicons/react/24/outline"; import SignOutButton from "../auth/SignOutButton"; const UserMenu = ({ isOpen, toggleDropDown }) => { @@ -10,14 +14,14 @@ const UserMenu = ({ isOpen, toggleDropDown }) => { const menuRef = useRef(null); useEffect(() => { - console.log(isOpen) + console.log(isOpen); function handleClickOutside(event) { if (menuRef.current && !menuRef.current.contains(event.target)) { toggleDropDown(); } } if (isOpen) { - document.addEventListener("click", handleClickOutside); + document.addEventListener("click", handleClickOutside); } return () => { document.removeEventListener("click", handleClickOutside); @@ -29,7 +33,7 @@ const UserMenu = ({ isOpen, toggleDropDown }) => { return (
    e.stopPropagation()} + onClick={(e) => e.stopPropagation()} className={`absolute right-2 top-12 w-56 ${isDarkTheme ? "bg-[#2F2F2F]" : "bg-white"} border border-gray-300 rounded-3xl shadow-sm z-20`} @@ -38,7 +42,9 @@ const UserMenu = ({ isOpen, toggleDropDown }) => {
    { navigate("/subscription"); }} @@ -47,6 +53,9 @@ const UserMenu = ({ isOpen, toggleDropDown }) => { { + navigate("/subscriptions"); + }} /> Subscription @@ -54,7 +63,9 @@ const UserMenu = ({ isOpen, toggleDropDown }) => {
    { navigate("/settings"); }} @@ -70,7 +81,9 @@ const UserMenu = ({ isOpen, toggleDropDown }) => {
    { navigate("/rules-and-policies"); }} @@ -85,8 +98,16 @@ const UserMenu = ({ isOpen, toggleDropDown }) => {
    -
    -
    +
    +
    From fa9d8d36e0d9dc5acef5fd7046e83d80fd7683fa Mon Sep 17 00:00:00 2001 From: Ekrem Beytula Date: Tue, 13 Aug 2024 09:39:25 +0300 Subject: [PATCH 32/45] toast on copying id --- src/client/CookingAppReact/src/pages/settings/Settings.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/client/CookingAppReact/src/pages/settings/Settings.jsx b/src/client/CookingAppReact/src/pages/settings/Settings.jsx index cc417e0c..4e15cf36 100644 --- a/src/client/CookingAppReact/src/pages/settings/Settings.jsx +++ b/src/client/CookingAppReact/src/pages/settings/Settings.jsx @@ -6,6 +6,7 @@ import { useSelector } from "react-redux"; import { getLoggedInUser } from "@/msal/userHelper"; import { getToken } from "@/msal/msal"; import { jwtDecode } from "jwt-decode"; +import toast from "react-hot-toast"; export default function Settings() { const isOpenRecipes = useSelector((state) => state.ui.recipesOpen); @@ -15,6 +16,7 @@ export default function Settings() { const token = await getToken(); const decoded = jwtDecode(token); await navigator.clipboard.writeText(decoded.sub); + toast.success("Success! Your ID has been copied to the clipboard."); }; return ( From 440b203662198f5de3a443a0f205d2987ce89918 Mon Sep 17 00:00:00 2001 From: Ekrem Beytula Date: Tue, 13 Aug 2024 09:51:47 +0300 Subject: [PATCH 33/45] . --- .../CookingAppReact/src/components/sidebar/Sidebar.jsx | 6 +----- .../CookingAppReact/src/components/userMenu/UserMenu.jsx | 8 +++++++- .../src/pages/subscribtion/SubscribtionDetails.jsx | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/client/CookingAppReact/src/components/sidebar/Sidebar.jsx b/src/client/CookingAppReact/src/components/sidebar/Sidebar.jsx index 91aacf33..a0070ec7 100644 --- a/src/client/CookingAppReact/src/components/sidebar/Sidebar.jsx +++ b/src/client/CookingAppReact/src/components/sidebar/Sidebar.jsx @@ -24,7 +24,7 @@ export default function Sidebar() { const chatPage = useSelector((state) => state.user.chatHistory.page); const chatHistory = useSelector((state) => state.user.chatHistory.chats); const totalPages = useSelector((state) => state.user.chatHistory.totalPages); - + let role = useSelector((state) => state.user.role.type); const selectChat = useSelectChat(); const dispatch = useDispatch(); const navigate = useNavigate(); @@ -92,8 +92,6 @@ export default function Sidebar() { } function isAdmin() { - let role = useSelector((state) => state.user.role.type); - if (role === "Admin") { return true; } else { @@ -102,8 +100,6 @@ export default function Sidebar() { } function isPremium() { - let role = useSelector((state) => state.user.role.type); - if (role === "Premium") { return true; } else { diff --git a/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx b/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx index c84a13bf..727d2f78 100644 --- a/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx +++ b/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx @@ -11,6 +11,8 @@ import SignOutButton from "../auth/SignOutButton"; const UserMenu = ({ isOpen, toggleDropDown }) => { const navigate = useNavigate(); const isDarkTheme = useSelector((state) => state.ui.isDarkTheme); + let role = useSelector((state) => state.user.role.type); + const menuRef = useRef(null); useEffect(() => { @@ -46,7 +48,11 @@ const UserMenu = ({ isOpen, toggleDropDown }) => { isDarkTheme ? "hover:bg-[#424242]" : "hover:bg-gray-100" } hover:rounded`} onClick={() => { - navigate("/subscription"); + if (role === "Premium") { + navigate("/subscription/manage"); + } else { + navigate("/subscription"); + } }} title="Subscription" > diff --git a/src/client/CookingAppReact/src/pages/subscribtion/SubscribtionDetails.jsx b/src/client/CookingAppReact/src/pages/subscribtion/SubscribtionDetails.jsx index ec633e4b..e3a34942 100644 --- a/src/client/CookingAppReact/src/pages/subscribtion/SubscribtionDetails.jsx +++ b/src/client/CookingAppReact/src/pages/subscribtion/SubscribtionDetails.jsx @@ -62,7 +62,7 @@ export default function SubscribtionDetails() { disabled={data.subscriptions[0].cancelAt ? true : false} onClick={handleCancellation} > - Cancel Subscribtion + Cancel Subscription
    stripe From e36c55fd2356f01e880635351b4fe0c8ff965571 Mon Sep 17 00:00:00 2001 From: Ekrem Beytula Date: Tue, 13 Aug 2024 09:56:51 +0300 Subject: [PATCH 34/45] Close menu on selection --- .../src/components/userMenu/UserMenu.jsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx b/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx index 727d2f78..b04f752c 100644 --- a/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx +++ b/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx @@ -1,6 +1,7 @@ import React, { useEffect, useRef } from "react"; import { useNavigate } from "react-router-dom"; -import { useSelector } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; +import { uiActions } from "@/store/uiSlice"; import { Cog6ToothIcon, CreditCardIcon, @@ -10,6 +11,7 @@ import SignOutButton from "../auth/SignOutButton"; const UserMenu = ({ isOpen, toggleDropDown }) => { const navigate = useNavigate(); + const dispatch = useDispatch(); const isDarkTheme = useSelector((state) => state.ui.isDarkTheme); let role = useSelector((state) => state.user.role.type); @@ -48,6 +50,7 @@ const UserMenu = ({ isOpen, toggleDropDown }) => { isDarkTheme ? "hover:bg-[#424242]" : "hover:bg-gray-100" } hover:rounded`} onClick={() => { + dispatch(uiActions.toggleDropdown()); if (role === "Premium") { navigate("/subscription/manage"); } else { @@ -59,9 +62,6 @@ const UserMenu = ({ isOpen, toggleDropDown }) => { { - navigate("/subscriptions"); - }} /> Subscription @@ -73,6 +73,7 @@ const UserMenu = ({ isOpen, toggleDropDown }) => { isDarkTheme ? "hover:bg-[#424242]" : "hover:bg-gray-100" } hover:rounded`} onClick={() => { + dispatch(uiActions.toggleDropdown()); navigate("/settings"); }} title="Settings" @@ -91,6 +92,7 @@ const UserMenu = ({ isOpen, toggleDropDown }) => { isDarkTheme ? "hover:bg-[#424242]" : "hover:bg-gray-100" } hover:rounded`} onClick={() => { + dispatch(uiActions.toggleDropdown()); navigate("/rules-and-policies"); }} title="Rules & Policies" From 28b36cc922bd6306f434b77e6c3720cc8f2cefbd Mon Sep 17 00:00:00 2001 From: Ekrem Beytula Date: Tue, 13 Aug 2024 09:57:03 +0300 Subject: [PATCH 35/45] Authorize subscription/manage --- src/client/CookingAppReact/src/App.jsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/client/CookingAppReact/src/App.jsx b/src/client/CookingAppReact/src/App.jsx index 05e86efa..0d4eeae6 100644 --- a/src/client/CookingAppReact/src/App.jsx +++ b/src/client/CookingAppReact/src/App.jsx @@ -48,7 +48,16 @@ const router = createBrowserRouter([ /> ), }, - { path: "subscription/manage", element: }, + { + path: "subscription/manage", + element: ( + } + requiredRole={"Premium"} + unAuthorizedPath={"/"} + /> + ), + }, { path: "success", element: }, { path: "settings", element: }, { path: "/rules-and-policies", element: }, From e3c762427bd566f25001da46a6582ab4602c9308 Mon Sep 17 00:00:00 2001 From: Ekrem Beytula Date: Tue, 13 Aug 2024 10:21:32 +0300 Subject: [PATCH 36/45] Store the name of the user --- src/server/CookingApp/Controllers/UserProfileController.cs | 2 +- .../CookingApp/Services/UserProfile/IUserProfileService.cs | 2 +- .../CookingApp/Services/UserProfile/UserProfileService.cs | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/server/CookingApp/Controllers/UserProfileController.cs b/src/server/CookingApp/Controllers/UserProfileController.cs index af2390e2..a48c89b4 100644 --- a/src/server/CookingApp/Controllers/UserProfileController.cs +++ b/src/server/CookingApp/Controllers/UserProfileController.cs @@ -20,7 +20,7 @@ public async Task FetchProfile() return new ApiResponse() { Status = 200, - Data = await userProfileService.FetchProfile(userId) + Data = await userProfileService.FetchProfile(userId,httpContextAccessor) }; } diff --git a/src/server/CookingApp/Services/UserProfile/IUserProfileService.cs b/src/server/CookingApp/Services/UserProfile/IUserProfileService.cs index e6e1118a..6746e803 100644 --- a/src/server/CookingApp/Services/UserProfile/IUserProfileService.cs +++ b/src/server/CookingApp/Services/UserProfile/IUserProfileService.cs @@ -4,7 +4,7 @@ namespace CookingApp.Services.UserProfile { public interface IUserProfileService { - Task FetchProfile(string userId); + Task FetchProfile(string userId,IHttpContextAccessor httpContextAccessor); Task ConfigurePreferences(ConfigurePreferencesRequest configureProfileRequest); diff --git a/src/server/CookingApp/Services/UserProfile/UserProfileService.cs b/src/server/CookingApp/Services/UserProfile/UserProfileService.cs index cfe3fead..26bf1eed 100644 --- a/src/server/CookingApp/Services/UserProfile/UserProfileService.cs +++ b/src/server/CookingApp/Services/UserProfile/UserProfileService.cs @@ -9,10 +9,10 @@ namespace CookingApp.Services.UserProfile { public class UserProfileService(IRepository profileRepo) : IUserProfileService { - public async Task FetchProfile(string userId) + public async Task FetchProfile(string userId,IHttpContextAccessor httpContextAccessor) { var profile = await profileRepo.GetFirstOrDefaultAsync(a => a.UserId == userId); - + var username = httpContextAccessor.HttpContext?.User.Claims.FirstOrDefault(claim=>claim.Type=="name"); if (profile is null) { profile = new Models.UserProfile @@ -23,6 +23,7 @@ public async Task FetchProfile(string userId) Allergies = new List(), AvoidedFoods = new List(), UserId = userId, + Name = username.Value }; await profileRepo.InsertAsync(profile); From dfbfe59878bc61cee8f9b452ab90a2ce8a7fe65b Mon Sep 17 00:00:00 2001 From: Ekrem Beytula Date: Tue, 13 Aug 2024 10:21:41 +0300 Subject: [PATCH 37/45] Show the name of the user --- src/client/CookingAppReact/src/hooks/useFetchUserStatus.js | 4 ++++ src/client/CookingAppReact/src/pages/settings/Settings.jsx | 4 ++-- src/client/CookingAppReact/src/store/userSlice.js | 7 +++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/client/CookingAppReact/src/hooks/useFetchUserStatus.js b/src/client/CookingAppReact/src/hooks/useFetchUserStatus.js index 3dda5336..41c1fd5e 100644 --- a/src/client/CookingAppReact/src/hooks/useFetchUserStatus.js +++ b/src/client/CookingAppReact/src/hooks/useFetchUserStatus.js @@ -14,9 +14,13 @@ const useFetchUserStatus = () => { const response = await checkUserStatus({ token }); if (response.status !== 401) { const body = await response.json(); + console.log(body); dispatch(uiActions.setTheme(body.data.interfacePreference.theme)); dispatch(uiActions.setLanguage(body.data.interfacePreference.language)); dispatch(userActions.setRole(body.data.role)); + dispatch( + userActions.setPersonal({ name: body.data.name, picture: null }) + ); dispatch( userActions.setDietaryPreferences({ allergies: body.data.allergies, diff --git a/src/client/CookingAppReact/src/pages/settings/Settings.jsx b/src/client/CookingAppReact/src/pages/settings/Settings.jsx index 4e15cf36..79b10124 100644 --- a/src/client/CookingAppReact/src/pages/settings/Settings.jsx +++ b/src/client/CookingAppReact/src/pages/settings/Settings.jsx @@ -11,7 +11,7 @@ import toast from "react-hot-toast"; export default function Settings() { const isOpenRecipes = useSelector((state) => state.ui.recipesOpen); const isOpenSideBar = useSelector((state) => state.ui.sidebarOpen); - + const personal = useSelector((state) => state.user.personal); const handleCopy = async () => { const token = await getToken(); const decoded = jwtDecode(token); @@ -36,7 +36,7 @@ export default function Settings() {

    - David Petkov + {personal.name}

    diff --git a/src/client/CookingAppReact/src/store/userSlice.js b/src/client/CookingAppReact/src/store/userSlice.js index 0a1eddb7..cd4b2803 100644 --- a/src/client/CookingAppReact/src/store/userSlice.js +++ b/src/client/CookingAppReact/src/store/userSlice.js @@ -1,9 +1,11 @@ +import { setProfilePicture } from "@/http/user"; import { createSlice } from "@reduxjs/toolkit"; const initialState = { selectedChat: { id: null, content: [], }, + profilePicture: null, personal: { name: "", picture: "", @@ -124,6 +126,9 @@ const userSlice = createSlice({ }, }; }, + setProfilePicture(state, action) { + state.profilePicture = action.payload; + }, }, }); From cad0e4dc7fae6be04881e6770c26ed658771880b Mon Sep 17 00:00:00 2001 From: David Hristov Date: Thu, 15 Aug 2024 10:51:19 +0300 Subject: [PATCH 42/45] fix pfp container to be circle --- src/client/CookingAppReact/src/pages/settings/Settings.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/CookingAppReact/src/pages/settings/Settings.jsx b/src/client/CookingAppReact/src/pages/settings/Settings.jsx index 848900f4..85563bbc 100644 --- a/src/client/CookingAppReact/src/pages/settings/Settings.jsx +++ b/src/client/CookingAppReact/src/pages/settings/Settings.jsx @@ -55,9 +55,9 @@ export default function Settings() { >

    Profile

    -
    +
    {pfp !== null ? ( - + ) : ( )} From b74ee0fc4edaa9ce3c6a7ec08e2fa2ef92c6e672 Mon Sep 17 00:00:00 2001 From: Ekrem Beytula Date: Thu, 15 Aug 2024 13:29:55 +0300 Subject: [PATCH 43/45] Theme beta --- .../screens/settings/Subscription.js | 2 +- src/client/CookingAppReact/package-lock.json | 54 ++-- src/client/CookingAppReact/package.json | 6 +- .../src/components/chat/BotResponse.jsx | 12 +- .../src/components/chat/ChatInput.jsx | 8 +- .../src/components/chat/Thinking.jsx | 34 +-- .../src/components/chat/UserMessage.jsx | 2 +- .../landingpage/ui/downloadmobile.jsx | 48 ++-- .../src/components/landingpage/ui/footer.jsx | 12 +- .../src/components/landingpage/ui/header.jsx | 14 +- .../src/components/landingpage/ui/title.jsx | 5 +- .../src/components/navbar/Navbar.jsx | 28 +- .../src/components/recipes/MyRecipes.jsx | 37 +-- .../src/components/recipes/RecipeCard.jsx | 2 +- .../settings/DietaryPreferences.jsx | 24 +- .../components/settings/FoodPreferences.jsx | 4 +- .../src/components/settings/Preferences.jsx | 4 +- .../src/components/settings/UserInterface.jsx | 10 +- .../src/components/sidebar/ChatItem.jsx | 8 +- .../src/components/sidebar/Sidebar.jsx | 12 +- .../subscriptions/SubscriptionPlanFree.jsx | 40 +-- .../subscriptions/SubscriptionPlanPremium.jsx | 47 ++-- .../src/components/ui/skeleton.jsx | 19 +- .../src/components/userMenu/UserMenu.jsx | 49 +--- .../foodPreferences/FoodPreferences.jsx | 245 +++++++++++++----- .../CookingAppReact/src/pages/admin/Admin.jsx | 4 +- .../src/pages/chat/EmptyChat.jsx | 2 +- .../src/pages/recipe/Recipe.jsx | 10 +- .../src/pages/settings/Settings.jsx | 12 +- .../subscribtion/SubscribtionDetails.jsx | 4 +- .../src/pages/subscribtion/Subscription.jsx | 20 +- .../CookingAppWeb/src/app/about/page.jsx | 58 ++++- .../src/app/archived-recipes/page.jsx | 79 +++++- .../CookingAppWeb/src/app/contacts/page.jsx | 62 ++++- .../src/app/food-preferences/page.jsx | 213 +++++++++++---- .../src/app/language-theme/page.jsx | 37 ++- .../src/app/recipes-details/page.jsx | 2 +- .../CookingAppWeb/src/app/recipes/page.jsx | 73 +++++- .../src/app/rules-policies/page.jsx | 138 ++++++++-- .../src/app/subscription/page.jsx | 202 ++++++++++++--- .../CookingAppWeb/src/app/user-menu/page.jsx | 198 +++++++++++--- .../CookingAppWeb/src/components/Recipe.jsx | 82 +++++- .../src/components/ThemeSwitcher.jsx | 11 +- .../src/components/auth/SignOutButton.jsx | 14 +- .../src/components/bot/ChatError.jsx | 15 +- .../src/components/bot/ChatInput.jsx | 58 ++++- .../CookingAppWeb/src/components/bot/Home.jsx | 148 +++++++++-- .../src/components/navigation/Sidebar.jsx | 49 +++- 48 files changed, 1648 insertions(+), 569 deletions(-) diff --git a/src/client/CookingAppFE/components/screens/settings/Subscription.js b/src/client/CookingAppFE/components/screens/settings/Subscription.js index 9a52206d..e1e30b24 100644 --- a/src/client/CookingAppFE/components/screens/settings/Subscription.js +++ b/src/client/CookingAppFE/components/screens/settings/Subscription.js @@ -177,7 +177,7 @@ const Subscription = () => { Heading for FAQs diff --git a/src/client/CookingAppReact/package-lock.json b/src/client/CookingAppReact/package-lock.json index d54549e8..08d76402 100644 --- a/src/client/CookingAppReact/package-lock.json +++ b/src/client/CookingAppReact/package-lock.json @@ -35,13 +35,13 @@ "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.1", - "autoprefixer": "^10.4.19", + "autoprefixer": "^10.4.20", "eslint": "^8.57.0", "eslint-plugin-react": "^7.34.3", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-refresh": "^0.4.7", - "postcss": "^8.4.40", - "tailwindcss": "^3.4.7", + "postcss": "^8.4.41", + "tailwindcss": "^3.4.10", "vite": "^5.3.4" } }, @@ -1813,9 +1813,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", "dev": true, "funding": [ { @@ -1832,11 +1832,11 @@ } ], "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", + "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "bin": { @@ -1902,9 +1902,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", - "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "dev": true, "funding": [ { @@ -1921,9 +1921,9 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001640", - "electron-to-chromium": "^1.4.820", - "node-releases": "^2.0.14", + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", "update-browserslist-db": "^1.1.0" }, "bin": { @@ -1970,9 +1970,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001643", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz", - "integrity": "sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==", + "version": "1.0.30001651", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", + "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", "dev": true, "funding": [ { @@ -2279,9 +2279,9 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "node_modules/electron-to-chromium": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.2.tgz", - "integrity": "sha512-kc4r3U3V3WLaaZqThjYz/Y6z8tJe+7K0bbjUVo3i+LWIypVdMx5nXCkwRe6SWbY6ILqLdc1rKcKmr3HoH7wjSQ==", + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.7.tgz", + "integrity": "sha512-6FTNWIWMxMy/ZY6799nBlPtF1DFDQ6VQJ7yyDP27SJNt5lwtQ5ufqVvHylb3fdQefvRcgA3fKcFMJi9OLwBRNw==", "dev": true }, "node_modules/emoji-regex": { @@ -4315,9 +4315,9 @@ } }, "node_modules/postcss": { - "version": "8.4.40", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz", - "integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==", + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", "funding": [ { "type": "opencollective", @@ -5365,9 +5365,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.7.tgz", - "integrity": "sha512-rxWZbe87YJb4OcSopb7up2Ba4U82BoiSGUdoDr3Ydrg9ckxFS/YWsvhN323GMcddgU65QRy7JndC7ahhInhvlQ==", + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.10.tgz", + "integrity": "sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==", "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", diff --git a/src/client/CookingAppReact/package.json b/src/client/CookingAppReact/package.json index d614b048..9e5e2ac4 100644 --- a/src/client/CookingAppReact/package.json +++ b/src/client/CookingAppReact/package.json @@ -37,13 +37,13 @@ "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.1", - "autoprefixer": "^10.4.19", + "autoprefixer": "^10.4.20", "eslint": "^8.57.0", "eslint-plugin-react": "^7.34.3", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-refresh": "^0.4.7", - "postcss": "^8.4.40", - "tailwindcss": "^3.4.7", + "postcss": "^8.4.41", + "tailwindcss": "^3.4.10", "vite": "^5.3.4" } } diff --git a/src/client/CookingAppReact/src/components/chat/BotResponse.jsx b/src/client/CookingAppReact/src/components/chat/BotResponse.jsx index 08e7249c..71a7d167 100644 --- a/src/client/CookingAppReact/src/components/chat/BotResponse.jsx +++ b/src/client/CookingAppReact/src/components/chat/BotResponse.jsx @@ -29,12 +29,14 @@ export default function BotResponse({ message }) { width={40} className="border shadow-sm rounded-full p-1 mr-5" /> -

    {message.content}

    +

    + {message.content} +

    {message.type === "Recipe" && role !== "Free" && (
    {isPending ? ( - + Crafting Meal . . @@ -60,7 +62,7 @@ export default function BotResponse({ message }) { {message.type === "Recipe" && role === "Free" && (
    -

    Get Premium

    +

    Get Premium

    )} diff --git a/src/client/CookingAppReact/src/components/chat/ChatInput.jsx b/src/client/CookingAppReact/src/components/chat/ChatInput.jsx index 4cbf2573..2d10bc55 100644 --- a/src/client/CookingAppReact/src/components/chat/ChatInput.jsx +++ b/src/client/CookingAppReact/src/components/chat/ChatInput.jsx @@ -106,8 +106,8 @@ export default function ChatInput() { }, [base64Image]); return ( -
    -
      +
      +
      • @@ -140,7 +140,7 @@ export default function ChatInput() { />
      -

      +

      This AI may occasionally make mistakes. Please verify any important information.

      diff --git a/src/client/CookingAppReact/src/components/chat/Thinking.jsx b/src/client/CookingAppReact/src/components/chat/Thinking.jsx index 1599a7e2..88031c06 100644 --- a/src/client/CookingAppReact/src/components/chat/Thinking.jsx +++ b/src/client/CookingAppReact/src/components/chat/Thinking.jsx @@ -3,7 +3,7 @@ import React from "react"; import logo from "/public/logo-master.png"; import { useSelector } from "react-redux"; import { FaSpinner } from "react-icons/fa"; -import '../../assets/css/animations.css'; +import "../../assets/css/animations.css"; const Thinking = () => { const isDarkTheme = useSelector((state) => state.ui.isDarkTheme); @@ -12,22 +12,22 @@ const Thinking = () => {
      - - - - - - -
      + + + + + + +
    ); diff --git a/src/client/CookingAppReact/src/components/chat/UserMessage.jsx b/src/client/CookingAppReact/src/components/chat/UserMessage.jsx index 93865838..99eb8a68 100644 --- a/src/client/CookingAppReact/src/components/chat/UserMessage.jsx +++ b/src/client/CookingAppReact/src/components/chat/UserMessage.jsx @@ -2,7 +2,7 @@ export default function UserMessage({ message }) { return (
  • {message.type === "Text" && ( -

    +

    {message.content}

    )} diff --git a/src/client/CookingAppReact/src/components/landingpage/ui/downloadmobile.jsx b/src/client/CookingAppReact/src/components/landingpage/ui/downloadmobile.jsx index ca6b9401..26963ecb 100644 --- a/src/client/CookingAppReact/src/components/landingpage/ui/downloadmobile.jsx +++ b/src/client/CookingAppReact/src/components/landingpage/ui/downloadmobile.jsx @@ -1,28 +1,38 @@ import PhoneImg from "../../../assets/landing/iphone4.png"; import QrImage from "../../../assets/landing/qrcode.png"; - -export default function DownloadMobile(){ - return ( -
    -
    -
    -
    -
    - -
    +export default function DownloadMobile() { + return ( +
    +
    +
    +
    +
    +
    -
    -

    DOWNLOAD
    OUR APP

    -

    Cooking has never been easier. Download the MealMaster app on your mobile device today and have a personal chef right in your pocket. Available on:

    -
    -
    +
    +
    +

    + DOWNLOAD +
    OUR APP +

    +

    + Cooking has never been easier. Download the MealMaster app on your + mobile device today and have a personal chef right in your pocket. + Available on: +

    +
    +
    -
    -
    +
    - ); -} \ No newline at end of file +
    + ); +} diff --git a/src/client/CookingAppReact/src/components/landingpage/ui/footer.jsx b/src/client/CookingAppReact/src/components/landingpage/ui/footer.jsx index c9fc981c..f8683ac8 100644 --- a/src/client/CookingAppReact/src/components/landingpage/ui/footer.jsx +++ b/src/client/CookingAppReact/src/components/landingpage/ui/footer.jsx @@ -2,11 +2,13 @@ import logo from "../../../../public/icon2.png"; export default function Footer() { return ( -