From bde796c3fd2eb0da6398ee3b1825bab93db76a2b Mon Sep 17 00:00:00 2001 From: David Hristov Date: Tue, 20 Aug 2024 12:36:38 +0300 Subject: [PATCH 1/8] add azure stuff to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index e6651368..86d24812 100644 --- a/.gitignore +++ b/.gitignore @@ -407,3 +407,5 @@ src/server/CookingApp/Properties/launchSettings.json src/server/CookingApp/Properties/launchSettings.json /src/server/CookingApp/appsettings.Development.json /src/server/CookingApp/Properties/launchSettings.json +/src/server/CookingApp/appsettings.Production.json +/src/server/CookingApp/Properties From c3bc15eb53e1723f9979814923d3b22ac224108f Mon Sep 17 00:00:00 2001 From: David Hristov Date: Tue, 20 Aug 2024 12:38:30 +0300 Subject: [PATCH 2/8] ignore properties folder --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index e6651368..84157557 100644 --- a/.gitignore +++ b/.gitignore @@ -407,3 +407,5 @@ src/server/CookingApp/Properties/launchSettings.json src/server/CookingApp/Properties/launchSettings.json /src/server/CookingApp/appsettings.Development.json /src/server/CookingApp/Properties/launchSettings.json +/src/server/CookingApp/Properties +/src/server/CookingApp/appsettings.Production.json From 8c58fd20ea9955c1289e423d38f84abd29ba683e Mon Sep 17 00:00:00 2001 From: David Hristov Date: Tue, 20 Aug 2024 13:20:33 +0300 Subject: [PATCH 3/8] rework recipe and chat generations and add 200 char limit --- .../src/components/chat/ChatInput.jsx | 7 ++++++- .../Services/Limitation/LimitationService.cs | 7 ++++--- .../CookingApp/Services/Message/MessageService.cs | 5 +++++ .../CookingApp/Services/Recipe/RecipeService.cs | 15 ++++++++++++++- .../CookingApp/ViewModels/Message/MessageData.cs | 1 + 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/client/CookingAppReact/src/components/chat/ChatInput.jsx b/src/client/CookingAppReact/src/components/chat/ChatInput.jsx index b72b0c1a..471b4fa6 100644 --- a/src/client/CookingAppReact/src/components/chat/ChatInput.jsx +++ b/src/client/CookingAppReact/src/components/chat/ChatInput.jsx @@ -17,6 +17,7 @@ export default function ChatInput() { const { mutate, isPending, error, isError } = useChat(); const dispatch = useDispatch(); const [base64Image, setBase64Image] = useState(null); + const [maxChars] = useState(200); useEffect(() => { if (role.limitations.chatGeneration === 10 && !isInitial) { toast( @@ -118,16 +119,20 @@ export default function ChatInput() { onClick={handleClick} /> -
  • +
  • +

    + {maxChars - input.length}/200 +

  • ProcessUserMessageLimitations(string userId) var today = DateTime.UtcNow; var chatDate = user.Role.Limitations.ChatFromDate; ArgumentNullException.ThrowIfNull(chatDate, nameof(chatDate)); - var endDate = chatDate.Value.AddHours(5); + var endDate = chatDate.Value.AddDays(1); if (today >= chatDate.Value && today <= endDate) { @@ -38,7 +38,7 @@ public async Task ProcessUserMessageLimitations(string userId) } user.Role.Limitations.ChatFromDate = today; - user.Role.Limitations.ChatGeneration = 20; + user.Role.Limitations.ChatGeneration = 50; await repo.UpdateAsync(user); return ProcessResult.MessageLimitationSuccessfull; @@ -64,7 +64,8 @@ public async Task ProcessUserMessageLimitations(string userId) } } - user.Role = CreateRole.Free(); + user.Role.Limitations.ChatFromDate = today; + user.Role.Limitations.ChatGeneration = 20; await repo.UpdateAsync(user); return ProcessResult.MessageLimitationSuccessfull; diff --git a/src/server/CookingApp/Services/Message/MessageService.cs b/src/server/CookingApp/Services/Message/MessageService.cs index 79254e18..c8a6d641 100644 --- a/src/server/CookingApp/Services/Message/MessageService.cs +++ b/src/server/CookingApp/Services/Message/MessageService.cs @@ -22,6 +22,11 @@ public partial class MessageService(ChatClient client, { public async Task SendMessage(string userId, MessageData request) { + if (request.Content.Length >= 200 && request.Type == MessageType.Text) + { + throw new ArgumentException(); + } + var chat = new Chat(); if (request.ChatId == null) chat = await chatService.CreateChat(userId); diff --git a/src/server/CookingApp/Services/Recipe/RecipeService.cs b/src/server/CookingApp/Services/Recipe/RecipeService.cs index fd2ac798..62622ac6 100644 --- a/src/server/CookingApp/Services/Recipe/RecipeService.cs +++ b/src/server/CookingApp/Services/Recipe/RecipeService.cs @@ -1,4 +1,5 @@ -using CookingApp.Infrastructure.Enums; +using System.Text.RegularExpressions; +using CookingApp.Infrastructure.Enums; using OpenAI.Chat; namespace CookingApp.Services.Recipe @@ -16,6 +17,18 @@ public class RecipeService(ChatClient client, IRepository repo, IImageSe /// public async Task CreateRecipe(string request, string userId) { + string pattern = @"Title:\s(?[A-Za-z ]+)"; + + var match = Regex.Match(request, pattern); + if (!match.Success) + { + throw new InvalidRecipeRequestException(); + } + var title = match.Groups["title"].Value; + if (await repo.ExistsAsync(r => r.Title == title)) + { + throw new InvalidRecipeRequestException(); + } var messages = new List<ChatMessage> { new SystemChatMessage(Completions.BuildRecipeConvertSystemMessage()), diff --git a/src/server/CookingApp/ViewModels/Message/MessageData.cs b/src/server/CookingApp/ViewModels/Message/MessageData.cs index 907eab6f..643f817e 100644 --- a/src/server/CookingApp/ViewModels/Message/MessageData.cs +++ b/src/server/CookingApp/ViewModels/Message/MessageData.cs @@ -7,5 +7,6 @@ public class MessageData public MessageType Type { get; set; } = default!; public string Content { get; set; } = default!; public string? ChatId { get; set; } + } } From ef48bc9002a2620c116703e294c3836ccbe20d47 Mon Sep 17 00:00:00 2001 From: David Hristov <orionvt3@gmail.com> Date: Tue, 20 Aug 2024 13:29:55 +0300 Subject: [PATCH 4/8] removed partial class --- src/server/CookingApp/Services/Message/MessageService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/CookingApp/Services/Message/MessageService.cs b/src/server/CookingApp/Services/Message/MessageService.cs index c8a6d641..b71048e4 100644 --- a/src/server/CookingApp/Services/Message/MessageService.cs +++ b/src/server/CookingApp/Services/Message/MessageService.cs @@ -14,7 +14,7 @@ using System; using CookingApp.Common.EntityConstants; - public partial class MessageService(ChatClient client, + public class MessageService(ChatClient client, IChatService chatService, IRepository<Chat> chatRepo, IRepository<UserProfile> profileRepo, From 6858934a2eadd002f01687acf76ec5f9538ec0cd Mon Sep 17 00:00:00 2001 From: David Petkov <davidpetkov2006@gmail.com> Date: Tue, 20 Aug 2024 15:11:28 +0300 Subject: [PATCH 5/8] Ui Updates --- .../src/components/chat/ChatInput.jsx | 18 ++- .../src/components/navbar/Navbar.jsx | 141 ++++++++++-------- .../src/components/recipes/MyRecipes.jsx | 24 ++- .../src/components/recipes/RecipeCard.jsx | 5 +- .../settings/DietaryPreferences.jsx | 2 +- .../src/components/settings/UserInterface.jsx | 3 +- .../src/components/sidebar/Sidebar.jsx | 48 ++++-- .../src/components/ui/mealIcon.jsx | 27 ++++ .../src/components/ui/tooltip.jsx | 34 +++++ .../src/components/userMenu/UserMenu.jsx | 2 +- .../src/pages/layout/Layout.jsx | 2 +- .../src/pages/recipe/Recipe.jsx | 38 +++-- .../src/pages/settings/Settings.jsx | 2 +- .../subscribtion/SubscriptionDetails.jsx | 28 +++- .../CookingAppReact/src/store/uiSlice.js | 2 +- src/client/CookingAppReact/tailwind.config.js | 1 + .../CookingApp/Properties/launchSettings.json | 2 +- 17 files changed, 262 insertions(+), 117 deletions(-) create mode 100644 src/client/CookingAppReact/src/components/ui/mealIcon.jsx create mode 100644 src/client/CookingAppReact/src/components/ui/tooltip.jsx diff --git a/src/client/CookingAppReact/src/components/chat/ChatInput.jsx b/src/client/CookingAppReact/src/components/chat/ChatInput.jsx index b72b0c1a..6b6c247a 100644 --- a/src/client/CookingAppReact/src/components/chat/ChatInput.jsx +++ b/src/client/CookingAppReact/src/components/chat/ChatInput.jsx @@ -8,6 +8,8 @@ import useChat from "../../hooks/useChat"; import { uiActions } from "../../store/uiSlice"; import toast from "react-hot-toast"; import { useRef } from "react"; +import Tooltip from "../ui/tooltip"; + export default function ChatInput() { const input = useSelector((state) => state.ui.input); const fileAttacher = useRef(); @@ -15,6 +17,8 @@ export default function ChatInput() { const isInitial = useSelector((state) => state.ui.isMessageWarningShowed); const role = useSelector((state) => state.user.role); const { mutate, isPending, error, isError } = useChat(); + const isOpenRecipes = useSelector((state) => state.ui.recipesOpen); + const isOpenSideBar = useSelector((state) => state.ui.sidebarOpen); const dispatch = useDispatch(); const [base64Image, setBase64Image] = useState(null); useEffect(() => { @@ -104,7 +108,11 @@ export default function ChatInput() { return ( <section className="flex flex-col items-center justify-center mb-5 w-full gap-1 "> - <ul className="flex w-4/5 md:w-3/5 lg:w-2/5 items-center secondary rounded-full gap-2 py-2 px-4 bg-active text-primaryText"> + <ul className={`flex + ${isOpenRecipes || isOpenSideBar ? "w-4/5 md:w-4/5 lg:w-3/5 xl:w-3/5" : "w-4/5 md:w-3/5 xl:w-2/5"} + items-center secondary rounded-full gap-2 py-2 px-4 bg-active text-primaryText`}> + + <Tooltip tooltipText="Attach Image"> <li> <input type="file" @@ -113,11 +121,13 @@ export default function ChatInput() { ref={fileAttacher} onChange={(event) => handleImageAttachment(event)} /> + <PaperClipIcon className="size-6 cursor-pointer" onClick={handleClick} /> </li> + </Tooltip> <li className="w-full"> <input type="text" @@ -128,6 +138,8 @@ export default function ChatInput() { className="w-full outline-none bg-active text-primaryText" /> </li> + + <Tooltip tooltipText="Send"> <li> <PaperAirplaneIcon className={`size-10 rounded-xl p-2 duration-200 ${ @@ -136,10 +148,10 @@ export default function ChatInput() { onClick={handleSubmission} /> </li> + </Tooltip> </ul> <p className="hidden md:inline text-sm opacity-80 text-primaryText"> - Meal Master may occasionally make mistakes. Please verify any important - information. + Meal Master may occasionally make mistakes. </p> </section> ); diff --git a/src/client/CookingAppReact/src/components/navbar/Navbar.jsx b/src/client/CookingAppReact/src/components/navbar/Navbar.jsx index fb0208bb..bd474cee 100644 --- a/src/client/CookingAppReact/src/components/navbar/Navbar.jsx +++ b/src/client/CookingAppReact/src/components/navbar/Navbar.jsx @@ -1,15 +1,14 @@ -import React, { useState } from "react"; +import React, { useState, useRef } from "react"; import { Bars3BottomLeftIcon } from "@heroicons/react/24/outline"; import { ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline"; import { UserIcon } from "@heroicons/react/24/outline"; -import { ClipboardDocumentCheckIcon } from "@heroicons/react/24/outline"; import { useDispatch, useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; import { uiActions } from "../../store/uiSlice"; import { userActions } from "@/store/userSlice"; -import Unlimited from "../../assets/unlimited.png"; -import SignOutButton from "../auth/SignOutButton"; import UserMenu from "../userMenu/UserMenu"; +import MealIcon from "../ui/mealIcon"; +import Tooltip from "../ui/tooltip"; export default function Navbar() { const dispatch = useDispatch(); @@ -37,71 +36,83 @@ export default function Navbar() { dispatch(uiActions.toggleDropdown()); }; + const [showTooltip, setShowTooltip] = useState(false); + const hoverTimeoutRef = useRef(null); + + const handleMouseEnter = () => { + hoverTimeoutRef.current = setTimeout(() => { + setShowTooltip(true); + }, 400); // 1 second delay + }; + + const handleMouseLeave = () => { + clearTimeout(hoverTimeoutRef.current); + setShowTooltip(false); + }; + return ( - <nav className="text-primaryText "> - <ul className={`flex flex-row w-full py-3 justify-between sticky`}> - <li - className={`flex flex-row pl-6 items-center gap-2 ${ - sidebarOpen ? "hidden" : "visible" - }`} - > + <nav className="text-primaryText"> + <ul className={`flex flex-row w-full py-3 justify-between sticky`}> + <li + className={`flex flex-row pl-6 items-center gap-2`} + > + <div className={`${ + sidebarOpen ? "hidden" : "" + }`}> + <Tooltip tooltipText="Sidebar"> <Bars3BottomLeftIcon - className="size-10 rounded-xl hover:bg-base hover:cursor-pointer p-2" + className="size-10 rounded-xl hover:bg-base hover:cursor-pointer p-2" onClick={handleSidebar} /> + </Tooltip> + + <Tooltip tooltipText="New Chat"> <ChatBubbleOvalLeftEllipsisIcon - className="size-10 hidden sm:block rounded-xl hover:bg-base hover:cursor-pointer p-2" + className="size-10 hidden xxs:block rounded-xl hover:bg-base hover:cursor-pointer p-2" onClick={handleNewChat} /> - <h2 className="font-semibold text-sm xs:text-xl flex flex-row justify-center content-center text-center h-full"> - <span className="text-center px-2 py-2 xs:py-1">Meal Master </span> - <span - className={`${ - role === "Free" ? "bg-base " : "bg-primary" - } text-center rounded-full flex justify-center items-center px-4 py-1`} - > - {role} - </span> - </h2> - {/* <h2 className="font-semibold text-xl flex flex-row justify-center items-center text-center content-center gap-2"> - <div className="flex justify-center items-center text-center border-2 border-orange-300 primaryText p-3 font-semibold text-xl rounded-full w-fit h-10"> - Meal Master - </div> - </h2> */} - </li> - <div - className={`flex flex-row pl-6 items-center gap-2 ${ - sidebarOpen ? "block" : "hidden" - }`} - > - <h2 className="font-semibold text-xl flex flex-row justify-center content-center text-center"> - <span className="text-center px-2 py-1 text-primaryText"> - Meal Master{" "} - </span> - <span - className={`${ - role === "Free" ? "bg-base" : "bg-primary" - } text-primaryText rounded-full px-4 py-1`} - > - {role} - </span> - </h2> - {/* <h2 className="font-semibold text-xl flex flex-row justify-center items-center text-center content-center gap-2"> - <div className="flex justify-center items-center text-center border-2 border-orange-300 primaryText p-3 font-semibold text-xl rounded-full w-fit h-10"> - Meal Master - </div> - </h2> */} + </Tooltip> </div> - <li - className={`right-0 sticky flex flex-row pr-10 items-center gap-5 ${ - recipesOpen ? "invisible" : "" - }`} - > - <ClipboardDocumentCheckIcon - className="size-10 rounded-xl hidden sm:block hover:bg-base hover:cursor-pointer p-2" + <h2 className={`font-semibold hidden md:flex text-lg flex-row justify-center items-center text-center h-full`}> + <span className={`hidden md:block text-center px-2 py-2 xs:py-1`}>Meal Master </span> + <span + className={`${ + role === "Free" ? "bg-base " : "bg-primary" + } text-center rounded-full flex justify-center items-center px-4 py-1`} + > + {role} + </span> + </h2> + </li> + <li + className={` font-semibold flex md:hidden text-lg flex-row justify-center items-center text-center h-full`} + > + <h2 className="font-semibold text-lg flex flex-row justify-center content-center text-center"> + <span className={`hidden md:block text-center px-2 py-2 xs:py-1`}>Meal Master </span> + <span + className={`${ + role === "Free" ? "bg-base" : "bg-primary" + } text-center rounded-full flex justify-center items-center px-4 py-1`} + > + {role} + </span> + </h2> + </li> + + <li + className={`flex flex-row items-center justify-center content-center text-center gap-2 visible pr-6 ${ + recipesOpen ? "invisible" : "" + }`} + > + <Tooltip tooltipText="My Meals"> + <div + className="size-10 rounded-xl hidden xxs:block hover:cursor-pointer p-2 hover:bg-base" onClick={handleRecipes} - /> - <div className="relative"> + > + <MealIcon className="group-hover:bg-base" /> + </div> + </Tooltip> + <Tooltip tooltipText="User Menu"> <UserIcon className="size-10 rounded-xl hover:bg-base hover:cursor-pointer p-2" onClick={(e) => { @@ -109,10 +120,10 @@ export default function Navbar() { toggleDropDown(); }} /> - <UserMenu isOpen={dropDownOpen} toggleDropDown={toggleDropDown} /> - </div> - </li> - </ul> - </nav> + </Tooltip> + <UserMenu isOpen={dropDownOpen} toggleDropDown={toggleDropDown} /> + </li> + </ul> + </nav> ); } diff --git a/src/client/CookingAppReact/src/components/recipes/MyRecipes.jsx b/src/client/CookingAppReact/src/components/recipes/MyRecipes.jsx index 0c18ed66..9758c4a3 100644 --- a/src/client/CookingAppReact/src/components/recipes/MyRecipes.jsx +++ b/src/client/CookingAppReact/src/components/recipes/MyRecipes.jsx @@ -1,6 +1,6 @@ import { useDispatch, useSelector } from "react-redux"; import { UserIcon } from "@heroicons/react/24/outline"; -import { ChevronLeftIcon } from "@heroicons/react/24/outline"; +import { ChevronLeftIcon, XMarkIcon } from "@heroicons/react/24/outline"; import { uiActions } from "../../store/uiSlice"; import RecipeCard from "./RecipeCard"; import { getToken } from "@/msal/msal"; @@ -10,6 +10,7 @@ import { Skeleton } from "../ui/skeleton"; import useFirstPageRecipes from "@/hooks/useFirstPageRecipes"; import "../../assets/css/animations.css"; import UserMenu from "../userMenu/UserMenu"; +import Tooltip from "../ui/tooltip"; export default function MyRecipes() { const isOpen = useSelector((state) => state.ui.recipesOpen); @@ -88,18 +89,26 @@ export default function MyRecipes() { <section className={`bg-base flex flex-col flex-shrink-0 ${ isOpen ? "visible w-screen md:w-[25rem] md:relative" : "invisible w-0" - } h-screen duration-300`} + } h-screen md:duration-300`} > <header className={`flex flex-row justify-between items-center px-3 py-3 text-primaryText ${ isOpen ? "" : "hidden" }`} > - <ChevronLeftIcon - className="size-10 rounded-xl border border-transparent hover:border hover:border-active hover:cursor-pointer p-2" - onClick={handleRecipes} - /> - <h1 className="text-lg text-primaryText">My Recipes</h1> + + <Tooltip tooltipText="Close"> + <XMarkIcon + className="size-10 rounded-xl border border-transparent hover:border hover:border-active hover:cursor-pointer p-2 md:hidden" + onClick={handleRecipes} + /> + <ChevronLeftIcon + className="size-10 rounded-xl border border-transparent hover:border hover:border-active hover:cursor-pointer p-2 hidden md:block" + onClick={handleRecipes} + /> + </Tooltip> + <h1 className="text-lg text-primaryText">My Meals</h1> + <Tooltip tooltipText="Profile"> <UserIcon className="size-10 invisible md:visible rounded-xl border border-transparent hover:border hover:border-active hover:cursor-pointer p-2" onClick={(e) => { @@ -107,6 +116,7 @@ export default function MyRecipes() { toggleDropDown(); }} /> + </Tooltip> <UserMenu isOpen={dropDownOpen} toggleDropDown={toggleDropDown} /> </header> diff --git a/src/client/CookingAppReact/src/components/recipes/RecipeCard.jsx b/src/client/CookingAppReact/src/components/recipes/RecipeCard.jsx index 622c2186..de71c7b0 100644 --- a/src/client/CookingAppReact/src/components/recipes/RecipeCard.jsx +++ b/src/client/CookingAppReact/src/components/recipes/RecipeCard.jsx @@ -15,7 +15,7 @@ export default function RecipeCard({ recipe }) { return ( <li key={recipe.id} - className="group flex flex-col text-primaryText bg-secondary w-full h-72 rounded-2xl pb-2 hover:cursor-pointer transition" + className="group flex flex-col justify-between text-primaryText bg-secondary w-full h-72 rounded-2xl pb-2 hover:cursor-pointer transition" onClick={handleClick} > <img @@ -30,9 +30,10 @@ export default function RecipeCard({ recipe }) { {recipe.title} </h2> </div> - <div className="w-full px-8 mb-3 text-gray-500 transition-all invisible group-hover:visible overflow-hidden"> + <div className="w-full px-8 text-gray-500 transition-all hidden group-hover:block overflow-hidden"> <p className="w-full">{recipe.description}</p> </div> + <div className="mb-3"></div> <section className="flex flex-row justify-between mb-2"> <div className="flex w-full flex-row justify-start gap-2 ms-4"> <ClockIcon className="size-6" /> diff --git a/src/client/CookingAppReact/src/components/settings/DietaryPreferences.jsx b/src/client/CookingAppReact/src/components/settings/DietaryPreferences.jsx index 4776ac2f..88e21255 100644 --- a/src/client/CookingAppReact/src/components/settings/DietaryPreferences.jsx +++ b/src/client/CookingAppReact/src/components/settings/DietaryPreferences.jsx @@ -19,7 +19,7 @@ export default function DietaryPreferences({ }) { return ( <div className="flex flex-col md:flex-row"> - <div className="md:w-1/2 mb-10 pb-6 m-1 bg-secondary rounded-xl border shadow-sm py-4 px-4"> + <div className="md:w-1/2 pb-6 m-1 bg-secondary rounded-xl border shadow-sm py-4 px-4"> <h2 className="text-lg font-semibold mb-4 primaryText">Allergens</h2> {allergens.length > 0 ? ( <div className="flex flex-wrap mb-4"> diff --git a/src/client/CookingAppReact/src/components/settings/UserInterface.jsx b/src/client/CookingAppReact/src/components/settings/UserInterface.jsx index e7a8d8c8..07e8ad44 100644 --- a/src/client/CookingAppReact/src/components/settings/UserInterface.jsx +++ b/src/client/CookingAppReact/src/components/settings/UserInterface.jsx @@ -43,7 +43,6 @@ export default function UserInterface() { {`Current: ${language}`} </option> <option value="English">English</option> - <option value="German">German</option> </select> <select className="border rounded-lg m-1 px-4 py-3 md:w-1/2 text-sm shadow-sm bg-secondary" @@ -53,7 +52,7 @@ export default function UserInterface() { {`Current: ${theme}`} </option> <option value="Light">Light</option> - <option value="Dark">Dark</option> + <option value="Dark">Dark Contrast</option> </select> </div> </> diff --git a/src/client/CookingAppReact/src/components/sidebar/Sidebar.jsx b/src/client/CookingAppReact/src/components/sidebar/Sidebar.jsx index 937c0985..879ca50a 100644 --- a/src/client/CookingAppReact/src/components/sidebar/Sidebar.jsx +++ b/src/client/CookingAppReact/src/components/sidebar/Sidebar.jsx @@ -3,9 +3,9 @@ import { Skeleton } from "@/components/ui/skeleton"; import { ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline"; import { useSelector } from "react-redux"; -import { Fragment } from "react"; +import { Fragment, useRef } from "react"; import { ChartPieIcon } from "@heroicons/react/24/outline"; -import { BanknotesIcon } from "@heroicons/react/24/outline"; +import { BanknotesIcon, UserIcon } from "@heroicons/react/24/outline"; import { useDispatch } from "react-redux"; import { jwtDecode } from "jwt-decode"; import { getToken } from "../../msal/msal"; @@ -18,6 +18,7 @@ import { orderedSections } from "../../utils/sidebar"; import { getSectionTitle } from "../../utils/sidebar"; import useSelectChat from "../../hooks/useSelectChat"; import { userActions } from "../../store/userSlice"; +import MealIcon from "../ui/mealIcon"; import "../../assets/css/animations.css"; export default function Sidebar() { const isOpen = useSelector((state) => state.ui.sidebarOpen); @@ -25,6 +26,7 @@ export default function Sidebar() { 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 initial = useRef(true); const selectChat = useSelectChat(); const dispatch = useDispatch(); const navigate = useNavigate(); @@ -37,9 +39,17 @@ export default function Sidebar() { const decoded = jwtDecode(token); getFirstPage({ token: token, userId: decoded.sub, pageIndex: 1 }); } + console.log(chatHistory); getFirstPageAsync(); } }, [isOpen]); + useEffect(() => { + if (initial) { + if (window.innerWidth > 1300) { + dispatch(uiActions.openSidebar()); + } + } + }, [initial]); function handleChatSelection(chatId) { if (window.innerWidth < 1300) { dispatch(uiActions.closeSidebar()); @@ -50,6 +60,7 @@ export default function Sidebar() { function handleNewChat() { dispatch(uiActions.clearActive()); dispatch(userActions.emptyChat()); + dispatch(uiActions.closeSidebar()); navigate("/"); } @@ -89,6 +100,14 @@ export default function Sidebar() { navigate("/subscription/manage"); } + function handleClickRecipes() { + if (window.innerWidth < 1300) { + dispatch(uiActions.closeSidebar()); + } + + dispatch(uiActions.toggleRecipes()); + } + function isAdmin() { if (role === "Admin") { return true; @@ -112,11 +131,11 @@ export default function Sidebar() { } return ( <section - className={`bg-base flex flex-col flex-shrink-0 text-primaryText ${ - isOpen - ? "visible w-screen absolute z-10 md:w-80 md:relative md:z-0" - : "invisible w-0" - } h-screen duration-300`} + className={`bg-base flex flex-col flex-shrink-0 text-primaryText ${ + isOpen + ? "w-screen fixed z-10 md:w-80 md:relative md:z-0" + : "invisible w-0" + } h-screen md:duration-300`} > <header className="flex justify-between px-4 py-4"> <Bars3BottomLeftIcon @@ -132,16 +151,25 @@ export default function Sidebar() { className={`${isPremium() || !isOpen ? "hidden" : ""}`} onClick={handleClickSubscribtion} > - <h5 className="hover:bg-active mt-5 rounded-lg m-3 px-5 py-2 flex flex-row justify-start items-center hover:cursor-pointer isolate bg-secondary shadow-sm ring-1 ring-black/5"> + <h5 className="hover:bg-primary mt-5 rounded-lg m-3 px-5 py-2 flex flex-row justify-start items-center hover:cursor-pointer isolate bg-secondary shadow-sm ring-1 ring-black/5"> <BanknotesIcon className="size-5 mr-5" /> Get Premium </h5> </button> + <button + className={`${!isOpen ? "hidden" : "xxs:hidden"}`} + onClick={handleClickRecipes} + > + <h5 className="hover:bg-primary mt-5 rounded-lg m-3 px-5 py-2 flex flex-row justify-start items-center hover:cursor-pointer isolate bg-secondary shadow-sm ring-1 ring-black/5"> + <div className="w-5 mr-5"><MealIcon /></div> + My Meals + </h5> + </button> <button className={`${!isPremium() || !isOpen ? "hidden" : ""}`} onClick={handleClickYourSubscribtion} > - <h5 className="hover:bg-active mt-5 rounded-lg m-3 px-5 py-2 flex flex-row justify-start items-center hover:cursor-pointer isolate bg-secondary shadow-sm ring-1 ring-black/5"> + <h5 className="hover:bg-primary mt-5 rounded-lg m-3 px-5 py-2 flex flex-row justify-start items-center hover:cursor-pointer isolate bg-secondary shadow-sm ring-1 ring-black/5"> <BanknotesIcon className="size-5 mr-5" /> Your Subscription </h5> @@ -150,7 +178,7 @@ export default function Sidebar() { className={`${!isAdmin() || !isOpen ? "hidden" : ""}`} onClick={handleClickDashboard} > - <h5 className="hover:bg-gray-300 mt-5 rounded-lg m-3 px-5 py-2 flex flex-row justify-start items-center hover:cursor-pointer isolate bg-white/20 shadow-sm ring-1 ring-black/5"> + <h5 className="hover:bg-primary mt-5 rounded-lg m-3 px-5 py-2 flex flex-row justify-start items-center hover:cursor-pointer isolate bg-white/20 shadow-sm ring-1 ring-black/5"> <ChartPieIcon className="size-5 mr-5" /> Dashboard </h5> diff --git a/src/client/CookingAppReact/src/components/ui/mealIcon.jsx b/src/client/CookingAppReact/src/components/ui/mealIcon.jsx new file mode 100644 index 00000000..ca31d938 --- /dev/null +++ b/src/client/CookingAppReact/src/components/ui/mealIcon.jsx @@ -0,0 +1,27 @@ +const MealIcon = () => ( + <svg + version="1.1" + id="Layer_1" + xmlns="http://www.w3.org/2000/svg" + xmlnsXlink="http://www.w3.org/1999/xlink" + x="0px" + y="0px" + viewBox="0 0 122.88 115.21" + style={{ enableBackground: "new 0 0 122.88 115.21" }} + xmlSpace="preserve" + > + <g> + <path className="stroke-current fill-current text-primaryText" // Outline color + strokeWidth="1" // Thickness of the outline + d="M29.03,100.46l20.79-25.21l9.51,12.13L41,110.69C33.98,119.61,20.99,110.21,29.03,100.46L29.03,100.46z M53.31,43.05 + c1.98-6.46,1.07-11.98-6.37-20.18L28.76,1c-2.58-3.03-8.66,1.42-6.12,5.09L37.18,24c2.75,3.34-2.36,7.76-5.2,4.32L16.94,9.8 + c-2.8-3.21-8.59,1.03-5.66,4.7c4.24,5.1,10.8,13.43,15.04,18.53c2.94,2.99-1.53,7.42-4.43,3.69L6.96,18.32 + c-2.19-2.38-5.77-0.9-6.72,1.88c-1.02,2.97,1.49,5.14,3.2,7.34L20.1,49.06c5.17,5.99,10.95,9.54,17.67,7.53 + c1.03-0.31,2.29-0.94,3.64-1.77l44.76,57.78c2.41,3.11,7.06,3.44,10.08,0.93l0.69-0.57c3.4-2.83,3.95-8,1.04-11.34L50.58,47.16 + C51.96,45.62,52.97,44.16,53.31,43.05L53.31,43.05z M65.98,55.65l7.37-8.94C63.87,23.21,99-8.11,116.03,6.29 + C136.72,23.8,105.97,66,84.36,55.57l-8.73,11.09L65.98,55.65L65.98,55.65z"/> + </g> + </svg> +); + +export default MealIcon; diff --git a/src/client/CookingAppReact/src/components/ui/tooltip.jsx b/src/client/CookingAppReact/src/components/ui/tooltip.jsx new file mode 100644 index 00000000..49ac8e2c --- /dev/null +++ b/src/client/CookingAppReact/src/components/ui/tooltip.jsx @@ -0,0 +1,34 @@ +import React, { useState, useRef } from 'react'; + +const Tooltip = ({ children, tooltipText }) => { + const [showTooltip, setShowTooltip] = useState(false); + const hoverTimeoutRef = useRef(null); + + const handleMouseEnter = () => { + hoverTimeoutRef.current = setTimeout(() => { + setShowTooltip(true); + }, 500); // 1 second delay + }; + + const handleMouseLeave = () => { + clearTimeout(hoverTimeoutRef.current); + setShowTooltip(false); + }; + + return ( + <div + className="relative inline-block" + onMouseEnter={handleMouseEnter} + onMouseLeave={handleMouseLeave} + > + {children} + {showTooltip && ( + <div className="absolute left-1/2 transform -translate-x-1/2 top-full mt-2 w-max px-2 py-1 text-xs text-white bg-black rounded-md shadow-lg opacity-100 transition-opacity duration-300"> + {tooltipText} + </div> + )} + </div> + ); +}; + +export default Tooltip; diff --git a/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx b/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx index 7f0351c3..a9193a11 100644 --- a/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx +++ b/src/client/CookingAppReact/src/components/userMenu/UserMenu.jsx @@ -39,7 +39,7 @@ const UserMenu = ({ isOpen, toggleDropDown }) => { onClick={(e) => e.stopPropagation()} className={`absolute right-2 top-12 w-56 ${isDarkTheme ? "bg-[#2F2F2F]" : "bg-secondary"} - border rounded-3xl shadow-sm z-20 text-primaryText`} + border rounded-3xl shadow-sm z-20 text-primaryText mt-3`} > <div className="flex flex-col p-4"> <div className="flex flex-col items-start w-full space-y-2"> diff --git a/src/client/CookingAppReact/src/pages/layout/Layout.jsx b/src/client/CookingAppReact/src/pages/layout/Layout.jsx index dff9a57c..eeb0bae8 100644 --- a/src/client/CookingAppReact/src/pages/layout/Layout.jsx +++ b/src/client/CookingAppReact/src/pages/layout/Layout.jsx @@ -13,7 +13,7 @@ export default function Layout() { <main className={`flex w-full first-line:overflow-hidden ${ theme === "Light" ? "light" : theme === "Dark" && "dark" - } bg-base`} + } bg-base overflow-x-hidden`} > <Sidebar /> <section className="flex w-screen flex-col overflow-hidden shrink rounded-none md:rounded-2xl bg-secondary border-none md:border m-0 md:m-1 h-screen md:h-[calc(100vh-1vh)]"> diff --git a/src/client/CookingAppReact/src/pages/recipe/Recipe.jsx b/src/client/CookingAppReact/src/pages/recipe/Recipe.jsx index 8aa3c034..aefab682 100644 --- a/src/client/CookingAppReact/src/pages/recipe/Recipe.jsx +++ b/src/client/CookingAppReact/src/pages/recipe/Recipe.jsx @@ -3,24 +3,29 @@ import { ClockIcon } from "@heroicons/react/24/outline"; import { UserGroupIcon } from "@heroicons/react/24/outline"; import { CalendarDaysIcon } from "@heroicons/react/24/outline"; import useRecipeDetails from "@/hooks/useRecipeDetails"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; import toast from "react-hot-toast"; -const iconImports = import.meta.glob("../../assets/stepsimages/*.png", { - eager: true, -}); -const icons = Object.values(iconImports).map((mod) => mod.default); - -function getFormattedDate(datetime) { - const date = new Date(datetime); - const day = date.getUTCDate(); - const month = date.getUTCMonth() + 1; - const year = date.getUTCFullYear(); - const formattedDate = `${month}/${day}/${year}`; - - return formattedDate; -} export default function Recipe() { + const iconImports = import.meta.glob("../../assets/stepsimages/*.png", { + eager: true, + }); + + const icons = Object.values(iconImports).map((mod) => mod.default); + const isOpenRecipes = useSelector((state) => state.ui.recipesOpen); + const isOpenSideBar = useSelector((state) => state.ui.sidebarOpen); + + function getFormattedDate(datetime) { + const date = new Date(datetime); + const day = date.getUTCDate(); + const month = date.getUTCMonth() + 1; + const year = date.getUTCFullYear(); + const formattedDate = `${month}/${day}/${year}`; + + return formattedDate; + } + let { recipeId } = useParams(); const { data, isPending, isError, refetch } = useRecipeDetails(recipeId); useEffect(() => { @@ -29,7 +34,8 @@ export default function Recipe() { } }, [isError]); return ( - <div className="flex w-full pl-5 text-primaryText pr-5 xl:pl-28 xl:pr-36 py-16 flex-col overflow-y-auto"> + <div className={`flex w-full pl-5 text-primaryText pr-5 + ${isOpenRecipes && isOpenSideBar ? "xl:pl-5 xl:pr-5" : "xl:pl-28 xl:pr-36"} py-16 flex-col overflow-y-auto`}> {data && !isError && ( <> <div className="w-full flex flex-col xl:flex-row xl:h-[32rem] bg-base rounded-2xl"> diff --git a/src/client/CookingAppReact/src/pages/settings/Settings.jsx b/src/client/CookingAppReact/src/pages/settings/Settings.jsx index 9d5daeba..f613a7c6 100644 --- a/src/client/CookingAppReact/src/pages/settings/Settings.jsx +++ b/src/client/CookingAppReact/src/pages/settings/Settings.jsx @@ -55,7 +55,7 @@ export default function Settings() { <h1 className="font-semibold text-lg mb-4">Profile</h1> <div className="flex flex-row justify-between rounded-2xl border py-5 px-5 items-center shadow-sm bg-base"> <div className="flex flex-row items-center rounded-full gap-2"> - {pfp !== null ? ( + {pfp ? ( <img src={pfp} className="rounded-full object-cover w-14 h-14" /> ) : ( <UserIcon className="size-10 mr-1" /> diff --git a/src/client/CookingAppReact/src/pages/subscribtion/SubscriptionDetails.jsx b/src/client/CookingAppReact/src/pages/subscribtion/SubscriptionDetails.jsx index 29ade93c..9d4238d3 100644 --- a/src/client/CookingAppReact/src/pages/subscribtion/SubscriptionDetails.jsx +++ b/src/client/CookingAppReact/src/pages/subscribtion/SubscriptionDetails.jsx @@ -15,6 +15,26 @@ export default function SubscriptionDetails() { const token = await getToken(); mutate({ token: token, subscriptionId: data.subscriptions[0].id }); } + + function formatDateIso(dateString) { + const date = new Date(dateString); + + // Adjust for time zone offset + const options = { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + timeZone: 'UTC', // Ensure the date is interpreted as UTC + hour12: true // Use 12-hour time format + }; + + // Format date + return new Intl.DateTimeFormat('en-US', options).format(date); + } + return ( <div className="flex flex-col lg:flex-row w-full h-full justify-start content-start items-start px-10 lg:pl-40 text-primaryText"> <div className="flex w-full justify-center content-start items-start flex-col h-full"> @@ -37,22 +57,18 @@ export default function SubscriptionDetails() { </li> </li> <li className="text-xl font-semibold border-b-2 border-transparent hover:border-b-2 hover:border-orange-400 selection:bg-orange-400"> - {`Created at ${data.subscriptions[0].created}`} + {`Created on ${formatDateIso(data.subscriptions[0].created)}`} </li> {data.subscriptions[0].cancelAt ? ( <li className="text-xl font-semibold border-b-2 border-transparent hover:border-b-2 hover:border-orange-400 selection:bg-orange-400"> - {`Your subscription has been cancelled and it will expire on ${data.subscriptions[0].cancelAt}`} + {`Your subscription has been cancelled and it will expire on ${formatDateIso(data.subscriptions[0].cancelAt)}`} </li> ) : ( <li className="text-xl font-semibold border-b-2 border-transparent hover:border-b-2 hover:border-orange-400 selection:bg-orange-400"> {`Your next charge will be on ${data.subscriptions[0].currentPeriodEnd}`} </li> )} - - <li className="text-xl font-semibold border-b-2 border-transparent hover:border-b-2 hover:border-orange-400 selection:bg-orange-400"> - • Free cancellation - </li> </ul> <button className={`text-white bg-black ${ diff --git a/src/client/CookingAppReact/src/store/uiSlice.js b/src/client/CookingAppReact/src/store/uiSlice.js index 2c5a766b..e1af2733 100644 --- a/src/client/CookingAppReact/src/store/uiSlice.js +++ b/src/client/CookingAppReact/src/store/uiSlice.js @@ -3,7 +3,7 @@ import { createSlice } from "@reduxjs/toolkit"; import { act } from "react"; const initialState = { - sidebarOpen: true, + sidebarOpen: false, recipesOpen: false, dropdownOpen: false, isInitial: true, diff --git a/src/client/CookingAppReact/tailwind.config.js b/src/client/CookingAppReact/tailwind.config.js index 1d825c5a..2d8de440 100644 --- a/src/client/CookingAppReact/tailwind.config.js +++ b/src/client/CookingAppReact/tailwind.config.js @@ -48,6 +48,7 @@ module.exports = { }, }, screens: { + xxs: "328px", xs: "396px", sm: "480px", md: "768px", diff --git a/src/server/CookingApp/Properties/launchSettings.json b/src/server/CookingApp/Properties/launchSettings.json index 1f9e41a0..53febc9a 100644 --- a/src/server/CookingApp/Properties/launchSettings.json +++ b/src/server/CookingApp/Properties/launchSettings.json @@ -8,7 +8,7 @@ "ASPNETCORE_ENVIRONMENT": "Development" }, "dotnetRunMessages": true, - "applicationUrl": "http://192.168.0.103:8000" + "applicationUrl": "http://192.168.100.221:8000" }, "https": { "commandName": "Project", From 8837b1d0ac6648abbd0aabe598dcdc57c59383f5 Mon Sep 17 00:00:00 2001 From: David Petkov <davidpetkov2006@gmail.com> Date: Tue, 20 Aug 2024 15:42:07 +0300 Subject: [PATCH 6/8] new icon --- .../src/components/navbar/Navbar.jsx | 2 +- .../src/components/ui/mealIcon.jsx | 86 +++++++++++++------ 2 files changed, 61 insertions(+), 27 deletions(-) diff --git a/src/client/CookingAppReact/src/components/navbar/Navbar.jsx b/src/client/CookingAppReact/src/components/navbar/Navbar.jsx index bd474cee..d8e98cfc 100644 --- a/src/client/CookingAppReact/src/components/navbar/Navbar.jsx +++ b/src/client/CookingAppReact/src/components/navbar/Navbar.jsx @@ -106,7 +106,7 @@ export default function Navbar() { > <Tooltip tooltipText="My Meals"> <div - className="size-10 rounded-xl hidden xxs:block hover:cursor-pointer p-2 hover:bg-base" + className="w-10 rounded-xl hidden xxs:block hover:cursor-pointer p-2 hover:bg-base" onClick={handleRecipes} > <MealIcon className="group-hover:bg-base" /> diff --git a/src/client/CookingAppReact/src/components/ui/mealIcon.jsx b/src/client/CookingAppReact/src/components/ui/mealIcon.jsx index ca31d938..c55a14b6 100644 --- a/src/client/CookingAppReact/src/components/ui/mealIcon.jsx +++ b/src/client/CookingAppReact/src/components/ui/mealIcon.jsx @@ -1,27 +1,61 @@ const MealIcon = () => ( - <svg - version="1.1" - id="Layer_1" - xmlns="http://www.w3.org/2000/svg" - xmlnsXlink="http://www.w3.org/1999/xlink" - x="0px" - y="0px" - viewBox="0 0 122.88 115.21" - style={{ enableBackground: "new 0 0 122.88 115.21" }} - xmlSpace="preserve" - > - <g> - <path className="stroke-current fill-current text-primaryText" // Outline color - strokeWidth="1" // Thickness of the outline - d="M29.03,100.46l20.79-25.21l9.51,12.13L41,110.69C33.98,119.61,20.99,110.21,29.03,100.46L29.03,100.46z M53.31,43.05 - c1.98-6.46,1.07-11.98-6.37-20.18L28.76,1c-2.58-3.03-8.66,1.42-6.12,5.09L37.18,24c2.75,3.34-2.36,7.76-5.2,4.32L16.94,9.8 - c-2.8-3.21-8.59,1.03-5.66,4.7c4.24,5.1,10.8,13.43,15.04,18.53c2.94,2.99-1.53,7.42-4.43,3.69L6.96,18.32 - c-2.19-2.38-5.77-0.9-6.72,1.88c-1.02,2.97,1.49,5.14,3.2,7.34L20.1,49.06c5.17,5.99,10.95,9.54,17.67,7.53 - c1.03-0.31,2.29-0.94,3.64-1.77l44.76,57.78c2.41,3.11,7.06,3.44,10.08,0.93l0.69-0.57c3.4-2.83,3.95-8,1.04-11.34L50.58,47.16 - C51.96,45.62,52.97,44.16,53.31,43.05L53.31,43.05z M65.98,55.65l7.37-8.94C63.87,23.21,99-8.11,116.03,6.29 - C136.72,23.8,105.97,66,84.36,55.57l-8.73,11.09L65.98,55.65L65.98,55.65z"/> - </g> - </svg> -); - -export default MealIcon; + <svg + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 512 512" + width="24px" + height="24px" + className="fill-current stroke-current text-primaryText" + > + <title>ionicons-v5-p + + + + + + + ); + + export default MealIcon; + \ No newline at end of file From 0f2002189e812a4fa7fdc101681c90ee37bad6dd Mon Sep 17 00:00:00 2001 From: David Hristov Date: Tue, 20 Aug 2024 20:22:16 +0300 Subject: [PATCH 7/8] add characters and fix navbar --- src/client/CookingAppReact/src/components/chat/ChatInput.jsx | 3 +++ src/client/CookingAppReact/src/components/navbar/Navbar.jsx | 4 ++-- src/server/CookingApp/Properties/launchSettings.json | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/client/CookingAppReact/src/components/chat/ChatInput.jsx b/src/client/CookingAppReact/src/components/chat/ChatInput.jsx index 912493ed..89097ac1 100644 --- a/src/client/CookingAppReact/src/components/chat/ChatInput.jsx +++ b/src/client/CookingAppReact/src/components/chat/ChatInput.jsx @@ -140,6 +140,9 @@ export default function ChatInput() { className="w-full outline-none bg-active text-primaryText" />
  • +

    + {maxChars - input.length}/200 +

  • diff --git a/src/client/CookingAppReact/src/components/navbar/Navbar.jsx b/src/client/CookingAppReact/src/components/navbar/Navbar.jsx index d8e98cfc..d9314ba3 100644 --- a/src/client/CookingAppReact/src/components/navbar/Navbar.jsx +++ b/src/client/CookingAppReact/src/components/navbar/Navbar.jsx @@ -52,12 +52,12 @@ export default function Navbar() { return (