From d7d1a0155bdd71e4d9adee4a31bdd1ce52beaae0 Mon Sep 17 00:00:00 2001 From: Antoine Dewez <44063631+Zewed@users.noreply.github.com> Date: Thu, 21 Mar 2024 00:01:21 -0700 Subject: [PATCH] feat(frontend): dark mode (#2369) # Description Please include a summary of the changes and the related issue. Please also include relevant motivation and context. ## Checklist before requesting a review Please delete options that are not relevant. - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented hard-to-understand areas - [ ] I have ideally added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged ## Screenshots (if appropriate): --- frontend/app/App.tsx | 29 ++--- .../MentionItem/MentionItem.module.scss | 3 +- .../MentionsList/MentionsList.module.scss | 6 +- .../MenuControlButton.module.scss | 3 +- .../components/ChatInput/index.module.scss | 5 +- .../KnowledgeToFeed.module.scss | 3 +- .../FromDocuments/FromDocuments.module.scss | 7 +- .../MessageRow/MessageRow.module.scss | 7 +- .../MessageContent/MessageContent.module.scss | 1 - .../QuestionBrain/QuestionBrain.module.scss | 8 +- .../QuestionBrain/QuestionBrain.tsx | 16 +-- .../QuestionPrompt/QuestionPompt.module.scss | 3 +- .../components/Sources/Sources.module.scss | 3 +- frontend/app/chat/[chatId]/page.module.scss | 3 +- frontend/app/colors.css | 48 ++++++++ frontend/app/globals.css | 68 ++++++++++++ frontend/app/layout.module.scss | 8 ++ frontend/app/layout.tsx | 7 +- frontend/app/search/page.module.scss | 9 +- frontend/app/search/page.tsx | 4 +- .../BrainItem/BrainItem.module.scss | 15 ++- .../components/BrainItem/BrainItem.tsx | 27 +++-- .../BrainsList/BrainsList.module.scss | 5 +- .../KnowledgeItem/KnowledgeItem.module.scss | 7 +- .../KnowledgeTable/KnowledgeTable.module.scss | 1 - .../PeopleTab/PeopleTab.module.scss | 6 +- .../SettingsTab/SettingsTab.module.scss | 10 +- .../ModelSelection/ModelSelection.module.scss | 13 +-- .../BrainCatalogue/BrainCatalogue.module.scss | 11 +- .../BrainCatalogue/BrainCatalogue.tsx | 3 + .../components/Stepper/Stepper.module.scss | 25 ++--- .../CurrentBrain/CurrentBrain.module.scss | 11 +- .../components/CurrentBrain/CurrentBrain.tsx | 24 ++-- frontend/lib/components/Menu/Menu.module.scss | 7 +- frontend/lib/components/Menu/Menu.tsx | 6 +- .../DiscussionButton.module.scss | 14 +-- .../MenuButton/MenuButton.module.scss | 11 +- .../ThreadsButton/ThreadsButton.module.scss | 3 +- .../ThreadItem/ThreadItem.module.scss | 9 +- .../ThreadsSection/ThreadsSection.module.scss | 3 +- .../PageHeader/PageHeader.module.scss | 4 +- .../lib/components/PageHeader/PageHeader.tsx | 20 ++++ .../SearchModal/SearchModal.module.scss | 3 +- .../PricingModal/StripePricingModal.tsx | 4 +- .../PricingTable/PricingTable.module.scss | 9 ++ .../components/PricingTable/PricingTable.tsx | 45 +++----- .../components/PricingTable/types/types.d.ts | 12 ++ .../UploadDocumentModal.module.scss | 3 +- .../CountrySelector.module.scss | 6 +- .../FoldableSection.module.scss | 9 +- .../lib/components/ui/Icon/Icon.module.scss | 27 +++-- .../ui/LoaderIcon/LoaderIcon.module.scss | 1 - .../MessageInfoBox/MessageInfoBox.module.scss | 27 +++-- .../ui/MessageInfoBox/MessageInfoBox.tsx | 11 +- .../lib/components/ui/Modal/Modal.module.scss | 13 ++- frontend/lib/components/ui/Modal/Modal.tsx | 4 +- frontend/lib/components/ui/ModalPayment.tsx | 105 ------------------ .../ui/OptionsModal/OptionsModal.module.scss | 10 +- .../ui/QuivrButton/QuivrButton.module.scss | 35 +++--- .../components/ui/QuivrButton/QuivrButton.tsx | 7 +- .../ui/SearchBar/SearchBar.module.scss | 12 +- .../SingleSelector/SingleSelector.module.scss | 17 ++- .../lib/components/ui/Tabs/Tabs.module.scss | 13 +-- .../lib/components/ui/Tag/Tag.module.scss | 5 +- .../TextAreaInput/TextAreaInput.module.scss | 8 +- .../ui/TextButton/TextButton.module.scss | 9 +- .../ui/TextInput/TextInput.module.scss | 7 +- .../components/ui/Tooltip/Tooltip.module.scss | 3 +- .../User-settings.provider.tsx | 76 +++++++++++++ .../hooks/useUserSettingsContext.tsx | 15 +++ frontend/lib/helpers/iconList.ts | 4 + frontend/lib/helpers/parseBoolean.ts | 7 ++ frontend/public/default_brain_image.png | Bin 0 -> 5259 bytes frontend/styles/_BoxShadow.module.scss | 4 + frontend/styles/_Colors.module.scss | 12 +- 75 files changed, 584 insertions(+), 415 deletions(-) create mode 100644 frontend/app/colors.css create mode 100644 frontend/app/layout.module.scss create mode 100644 frontend/lib/components/Stripe/PricingModal/components/PricingTable/PricingTable.module.scss create mode 100644 frontend/lib/components/Stripe/PricingModal/components/PricingTable/types/types.d.ts delete mode 100644 frontend/lib/components/ui/ModalPayment.tsx create mode 100644 frontend/lib/context/UserSettingsProvider/User-settings.provider.tsx create mode 100644 frontend/lib/context/UserSettingsProvider/hooks/useUserSettingsContext.tsx create mode 100644 frontend/lib/helpers/parseBoolean.ts create mode 100644 frontend/public/default_brain_image.png create mode 100644 frontend/styles/_BoxShadow.module.scss diff --git a/frontend/app/App.tsx b/frontend/app/App.tsx index 3b533c3ca..c56b85efd 100644 --- a/frontend/app/App.tsx +++ b/frontend/app/App.tsx @@ -20,6 +20,7 @@ import { ChatsProvider } from "@/lib/context/ChatsProvider"; import { MenuProvider } from "@/lib/context/MenuProvider/Menu-provider"; import { SearchModalProvider } from "@/lib/context/SearchModalProvider/search-modal-provider"; import { useSupabase } from "@/lib/context/SupabaseProvider"; +import { UserSettingsProvider } from "@/lib/context/UserSettingsProvider/User-settings.provider"; import { IntercomProvider } from "@/lib/helpers/intercom/IntercomProvider"; import { UpdateMetadata } from "@/lib/helpers/updateMetadata"; import { usePageTracking } from "@/services/analytics/june/usePageTracking"; @@ -90,19 +91,21 @@ const queryClient = new QueryClient(); const AppWithQueryClient = ({ children }: PropsWithChildren): JSX.Element => { return ( - - - - - - - {children} - - - - - - + + + + + + + + {children} + + + + + + + ); }; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionItem/MentionItem.module.scss b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionItem/MentionItem.module.scss index 55774e820..b9a76da8d 100644 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionItem/MentionItem.module.scss +++ b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionItem/MentionItem.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Radius.module.scss"; @use "@/styles/Spacings.module.scss"; @use "@/styles/Typography.module.scss"; @@ -15,7 +14,7 @@ &:hover, &.selected { - background-color: Colors.$primary-light; + background-color: var(--background-special-1); font-weight: 550; } diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionsList.module.scss b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionsList.module.scss index 2a6f5b005..c794c74bb 100644 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionsList.module.scss +++ b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionsList.module.scss @@ -1,13 +1,13 @@ -@use "@/styles/Colors.module.scss"; +@use "@/styles/BoxShadow.module.scss"; @use "@/styles/Radius.module.scss"; @use "@/styles/Spacings.module.scss"; .mentions_list_wrapper { display: flex; - background-color: Colors.$white; + background-color: var(--background-0); flex-direction: column; padding: Spacings.$spacing03; - box-shadow: 0 1px 2px rgb(0, 0, 0, 0.25); + box-shadow: BoxShadow.$small; border-radius: Radius.$normal; gap: Spacings.$spacing03; max-width: 300px; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/MenuControlButton/MenuControlButton.module.scss b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/MenuControlButton/MenuControlButton.module.scss index 8568abb6c..ab92cc3f0 100644 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/MenuControlButton/MenuControlButton.module.scss +++ b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/MenuControlButton/MenuControlButton.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/IconSizes.module.scss"; @use "@/styles/Spacings.module.scss"; @@ -8,6 +7,6 @@ cursor: pointer; &:hover { - color: Colors.$accent; + color: var(--accent); } } diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/index.module.scss b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/index.module.scss index a774ddbb2..8b99139a3 100644 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/index.module.scss +++ b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/index.module.scss @@ -1,14 +1,13 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Radius.module.scss"; @use "@/styles/Spacings.module.scss"; .chat_container { display: flex; flex-direction: column; - background-color: Colors.$white; + background-color: var(--background-0); gap: Spacings.$spacing03; border-radius: Radius.$big; - border: 1px solid Colors.$lighter-grey; + border: 1px solid var(--border-0); overflow: hidden; .chat_wrapper { diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/KnowledgeToFeed.module.scss b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/KnowledgeToFeed.module.scss index db6d9ff01..0beb0cfad 100644 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/KnowledgeToFeed.module.scss +++ b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/KnowledgeToFeed.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/ScreenSizes.module.scss"; @use "@/styles/Spacings.module.scss"; @use "@/styles/Typography.module.scss"; @@ -26,7 +25,7 @@ } .uploaded_knowledges_title { - color: Colors.$dark-grey; + color: var(--text-2); display: flex; justify-content: space-between; } diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromDocuments/FromDocuments.module.scss b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromDocuments/FromDocuments.module.scss index e539b8bd1..de462b71e 100644 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromDocuments/FromDocuments.module.scss +++ b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/FromDocuments/FromDocuments.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Radius.module.scss"; @use "@/styles/ScreenSizes.module.scss"; @use "@/styles/Spacings.module.scss"; @@ -11,14 +10,14 @@ column-gap: Spacings.$spacing05; justify-content: center; align-items: center; - border: 1px dashed Colors.$lighter-grey; + border: 1px dashed var(--border-0); border-radius: Radius.$big; box-sizing: border-box; cursor: pointer; &.dragging { - border: 3px dashed Colors.$accent; - background-color: Colors.$lightest-black; + border: 3px dashed var(--accent); + background-color: var(--background-3); } .input { diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/MessageRow.module.scss b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/MessageRow.module.scss index a87e32c47..fc6357af6 100644 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/MessageRow.module.scss +++ b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/MessageRow.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Radius.module.scss"; @use "@/styles/Spacings.module.scss"; @@ -30,11 +29,11 @@ display: flex; gap: Spacings.$spacing02; align-items: center; - color: Colors.$dark-grey; + color: var(--text-2); } .message_row_content { - background-color: Colors.$lightest-grey; + background-color: var(--background-2); } } @@ -47,7 +46,7 @@ .message_row_content { align-self: flex-start; - background-color: Colors.$primary-lightest; + background-color: var(--background-special-0); margin-left: 1px; position: relative; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/components/MessageContent/MessageContent.module.scss b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/components/MessageContent/MessageContent.module.scss index f068d333a..6632b4528 100644 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/components/MessageContent/MessageContent.module.scss +++ b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/components/MessageContent/MessageContent.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Spacings.module.scss"; @use "@/styles/Typography.module.scss"; diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/components/QuestionBrain/QuestionBrain.module.scss b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/components/QuestionBrain/QuestionBrain.module.scss index 3781e4cfd..a67cbd2f5 100644 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/components/QuestionBrain/QuestionBrain.module.scss +++ b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/components/QuestionBrain/QuestionBrain.module.scss @@ -1,4 +1,4 @@ -@use "@/styles/Colors.module.scss"; +@use "@/styles/Radius.module.scss"; @use "@/styles/Spacings.module.scss"; @use "@/styles/Typography.module.scss"; @@ -6,9 +6,13 @@ display: flex; align-items: center; gap: Spacings.$spacing02; - color: Colors.$black; + color: var(--text-3); overflow: hidden; + .dark_image { + filter: invert(100%); + } + .brain_name { @include Typography.EllipsisOverflow; } diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/components/QuestionBrain/QuestionBrain.tsx b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/components/QuestionBrain/QuestionBrain.tsx index 2f0a5002b..df2a59fe9 100644 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/components/QuestionBrain/QuestionBrain.tsx +++ b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/components/QuestionBrain/QuestionBrain.tsx @@ -2,7 +2,7 @@ import Image from "next/image"; import { Fragment, useEffect, useState } from "react"; import { useBrainApi } from "@/lib/api/brain/useBrainApi"; -import Icon from "@/lib/components/ui/Icon/Icon"; +import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; import styles from "./QuestionBrain.module.scss"; @@ -17,7 +17,7 @@ export const QuestionBrain = ({ const [brainLogoUrl, setBrainLogoUrl] = useState( undefined ); - + const { isDarkMode } = useUserSettingsContext(); const { getBrain } = useBrainApi(); const getBrainLogoUrl = async () => { @@ -41,11 +41,13 @@ export const QuestionBrain = ({ return (
- {brainLogoUrl ? ( - brainLogo - ) : ( - - )} + logo_image {brainName}
); diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/components/QuestionPrompt/QuestionPompt.module.scss b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/components/QuestionPrompt/QuestionPompt.module.scss index 46a4e62aa..fad91c4f2 100644 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/components/QuestionPrompt/QuestionPompt.module.scss +++ b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/components/QuestionPrompt/QuestionPompt.module.scss @@ -1,11 +1,10 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Spacings.module.scss"; @use "@/styles/Typography.module.scss"; .prompt_name_wrapper { display: flex; align-items: center; - color: Colors.$black; + color: var(--text-3); font-size: Typography.$small; overflow: hidden; diff --git a/frontend/app/chat/[chatId]/components/Sources/Sources.module.scss b/frontend/app/chat/[chatId]/components/Sources/Sources.module.scss index 580071a41..03401ed95 100644 --- a/frontend/app/chat/[chatId]/components/Sources/Sources.module.scss +++ b/frontend/app/chat/[chatId]/components/Sources/Sources.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Radius.module.scss"; @use "@/styles/Spacings.module.scss"; @use "@/styles/Typography.module.scss"; @@ -11,7 +10,7 @@ gap: Spacings.$spacing03; overflow: hidden; height: 100%; - border: 1px solid Colors.$lightest-black; + border: 1px solid var(--border-1); border-radius: Radius.$big; .title_wrapper { diff --git a/frontend/app/chat/[chatId]/page.module.scss b/frontend/app/chat/[chatId]/page.module.scss index fbb48b93a..422a6204c 100644 --- a/frontend/app/chat/[chatId]/page.module.scss +++ b/frontend/app/chat/[chatId]/page.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Spacings.module.scss"; .main_container { @@ -9,7 +8,7 @@ .chat_page_container { display: flex; flex: 1 1 0%; - background-color: Colors.$white; + background-color: var(--background-0); padding-block: Spacings.$spacing06; padding-inline: Spacings.$spacing09; display: flex; diff --git a/frontend/app/colors.css b/frontend/app/colors.css new file mode 100644 index 000000000..57bbf229c --- /dev/null +++ b/frontend/app/colors.css @@ -0,0 +1,48 @@ +:root { + /* White */ + --white-0: #ffffff; + --white-1: #fafafa; + --white-2: #fcfaf6; + + /* Black */ + --black-0: #111111; + --black-1: #171717; + --black-2: #1c1c1c; + --black-3: #222222; + --black-4: #272727; + --black-5: #2d2d2d; + --black-6: #323232; + --black-7: #383838; + + /* Grey */ + --grey-O: #707070; + --grey-1: #c8c8c8; + --grey-2: #cbcbcb; + --grey-3: #e7e9ec; + --grey-4: #d3d3d3; + --grey-5: #f5f5f5; + + /* Primary */ + --primary-0: #6142d4; + --primary-1: #d0c6f2; + --primary-2: #f5f3fd; + + /* Accent */ + --accent: #13abba; + + /* Gold */ + --gold: #b8860b; + + /* Error */ + --dangerous-dark: #e30c17; + --dangerous: #9b373c; + --dangerous-lightest: #eedddd; + + /* Warning */ + --warning: #c77d33; + --warning-lightest: #e9d9c9; + + /* Success */ + --success: #47a455; + --success-lightest: #d0edd4; +} \ No newline at end of file diff --git a/frontend/app/globals.css b/frontend/app/globals.css index c207dceb6..65c36ede3 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -1,6 +1,7 @@ @import "tailwindcss/base"; @import "tailwindcss/components"; @import "tailwindcss/utilities"; +@import './colors.css'; main { @apply max-w-screen-xl mx-auto flex flex-col; @@ -48,3 +49,70 @@ div:focus { @apply h-[100vh] supports-[height:100cqh]:h-[100cqh] supports-[height:100svh]:h-[100svh]; } } + +:root { + /* Backgrounds */ + --background-0: var(--white-0); + --background-1: var(--white-1); + --background-2: var(--grey-5); + --background-3: var(--grey-3); + --background-4: var(--grey-0); + --background-5: var(--black-0); + --background-special-0: var(--primary-2); + --background-special-1: var(--primary-1); + --background-blur: rgba(0, 0, 0, 0.9); + + /* Borders */ + --border-0: var(--grey-4); + --border-1: var(--grey-3); + --border-2: var(--grey-1); + + /* Icons */ + --icon-0: var(--white-0); + --icon-1: var(--grey-1); + --icon-2: var(--grey-0); + --icon-3: var(--black-0); + + /* Text */ + --text-0: var(--white-0); + --text-1: var(--grey-1); + --text-2: var(--grey-0); + --text-3: var(--black-0); + + /* Box Shadow */ + --box-shadow: rgba(0, 0, 0, 0.25); +} + +body.dark_mode { + /* Backgrounds */ + --background-0: var(--black-0); + --background-1: var(--black-1); + --background-2: var(--black-2); + --background-3: var(--black-3); + --background-4: var(--black-4); + --background-5: var(--black-5); + --background-special-0: var(--black-3); + --background-special-1: var(--black-5); + --background-opposite: var(--white-0); + --background-blur: rgba(0, 0, 0, 0.9); + + /* Borders */ + --border-0: var(--white-0); + --border-1: var(--grey-1); + --border-2: var(--grey-2); + + /* Icons */ + --icon-0: var(--black-0); + --icon-1: var(--grey-0); + --icon-2: var(--grey-1); + --icon-3: var(--white-0); + + /* Text */ + --text-0: var(--black-0); + --text-1: var(--grey-0); + --text-2: var(--grey-1); + --text-3: var(--white-0); + + /* Box Shadow */ + --box-shadow: rgba(255, 255, 255, 0.25); +} \ No newline at end of file diff --git a/frontend/app/layout.module.scss b/frontend/app/layout.module.scss new file mode 100644 index 000000000..965563b8e --- /dev/null +++ b/frontend/app/layout.module.scss @@ -0,0 +1,8 @@ +.body { + color: var(--text-3); + background-color: var(--background-0); + display: flex; + flex-direction: column; + width: 100%; + height: 100vh; +} diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx index 56fa421bb..475266bbf 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -1,6 +1,5 @@ import { createServerComponentSupabaseClient } from "@supabase/auth-helpers-nextjs"; import { Analytics as VercelAnalytics } from "@vercel/analytics/react"; -import { Outfit } from "next/font/google"; import { cookies, headers } from "next/headers"; import { ToastProvider } from "@/lib/components/ui/Toast"; @@ -8,8 +7,7 @@ import { SupabaseProvider } from "@/lib/context/SupabaseProvider"; import { App } from "./App"; import "./globals.css"; - -const inter = Outfit({ subsets: ["latin"] }); +import styles from "./layout.module.scss"; export const metadata = { title: "Quivr - Get a Second Brain with Generative AI", @@ -34,7 +32,8 @@ const RootLayout = async ({ return ( diff --git a/frontend/app/search/page.module.scss b/frontend/app/search/page.module.scss index fdcd8cfa3..aa86d4dba 100644 --- a/frontend/app/search/page.module.scss +++ b/frontend/app/search/page.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/IconSizes.module.scss"; @use "@/styles/Radius.module.scss"; @use "@/styles/ScreenSizes.module.scss"; @@ -17,7 +16,7 @@ } .search_page_container { - background-color: Colors.$white; + background-color: var(--background-0); width: 100%; height: 100%; display: flex; @@ -48,14 +47,14 @@ @include Typography.Big; .quivr_text_primary { - color: Colors.$primary; + color: var(--primary-0); } } } } .shortcuts_card_wrapper { - background-color: Colors.$lightest-grey; + background-color: var(--background-2); padding: Spacings.$spacing05; gap: Spacings.$spacing03; border-radius: Radius.$big; @@ -66,7 +65,7 @@ gap: Spacings.$spacing02; .shortcut { - color: Colors.$primary; + color: var(--primary-0); } } } diff --git a/frontend/app/search/page.tsx b/frontend/app/search/page.tsx index 6f899e114..5a623d464 100644 --- a/frontend/app/search/page.tsx +++ b/frontend/app/search/page.tsx @@ -9,6 +9,7 @@ import PageHeader from "@/lib/components/PageHeader/PageHeader"; import { UploadDocumentModal } from "@/lib/components/UploadDocumentModal/UploadDocumentModal"; import { SearchBar } from "@/lib/components/ui/SearchBar/SearchBar"; import { useSupabase } from "@/lib/context/SupabaseProvider"; +import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; import { redirectToLogin } from "@/lib/router/redirectToLogin"; import { ButtonType } from "@/lib/types/QuivrButton"; @@ -18,6 +19,7 @@ const Search = (): JSX.Element => { const pathname = usePathname(); const { session } = useSupabase(); const { setIsBrainCreationModalOpened } = useBrainCreationContext(); + const { isDarkMode } = useUserSettingsContext(); useEffect(() => { if (session === null) { @@ -44,7 +46,7 @@ const Search = (): JSX.Element => {
- +
Talk to Quivr diff --git a/frontend/app/studio/BrainsTabs/components/BrainItem/BrainItem.module.scss b/frontend/app/studio/BrainsTabs/components/BrainItem/BrainItem.module.scss index 23392bded..bfdbbd0dc 100644 --- a/frontend/app/studio/BrainsTabs/components/BrainItem/BrainItem.module.scss +++ b/frontend/app/studio/BrainsTabs/components/BrainItem/BrainItem.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Radius.module.scss"; @use "@/styles/ScreenSizes.module.scss"; @use "@/styles/Spacings.module.scss"; @@ -14,15 +13,19 @@ align-items: center; cursor: pointer; margin-block: Spacings.$spacing03; - border: 1px solid Colors.$lightest-black; + border: 1px solid var(--border-1); border-radius: Radius.$normal; padding-block: Spacings.$spacing03; position: relative; overflow: visible; &:hover { - border-color: Colors.$primary; - background-color: Colors.$primary-lightest; + border-color: var(--primary-0); + background-color: var(--background-special-0); + } + + .dark_image { + filter: invert(100%); } .brain_info_wrapper { @@ -37,13 +40,13 @@ .name { @include Typography.EllipsisOverflow; width: 200px; - color: Colors.$black; + color: var(--text-3); } .description { @include Typography.EllipsisOverflow; flex: 1; - color: Colors.$dark-grey; + color: var(--text-2); } @media (max-width: ScreenSizes.$small) { diff --git a/frontend/app/studio/BrainsTabs/components/BrainItem/BrainItem.tsx b/frontend/app/studio/BrainsTabs/components/BrainItem/BrainItem.tsx index f877a51b6..325d106e3 100644 --- a/frontend/app/studio/BrainsTabs/components/BrainItem/BrainItem.tsx +++ b/frontend/app/studio/BrainsTabs/components/BrainItem/BrainItem.tsx @@ -9,6 +9,7 @@ import Icon from "@/lib/components/ui/Icon/Icon"; import { OptionsModal } from "@/lib/components/ui/OptionsModal/OptionsModal"; import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; import { MinimalBrainForUser } from "@/lib/context/BrainProvider/types"; +import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; import { Option } from "@/lib/types/Options"; import styles from "./BrainItem.module.scss"; @@ -32,6 +33,7 @@ export const BrainItem = ({ brain, even }: BrainItemProps): JSX.Element => { brainId: brain.id, userAccessibleBrains: allBrains, }); + const { isDarkMode } = useUserSettingsContext(); const iconRef = useRef(null); const optionsRef = useRef(null); @@ -67,7 +69,7 @@ export const BrainItem = ({ brain, even }: BrainItemProps): JSX.Element => { return () => { document.removeEventListener("mousedown", handleClickOutside); }; - }, []); + }, [isDarkMode]); return ( <> @@ -77,16 +79,18 @@ export const BrainItem = ({ brain, even }: BrainItemProps): JSX.Element => { ${styles.brain_item_wrapper} `} > - {brain.integration_logo_url ? ( - logo_image - ) : ( - - )} + logo_image + { {brain.name} {brain.description} -
{ const { setCurrentSelectedBrain, currentSelectedBrain } = useBrainCreationContext(); + const { isDarkMode } = useUserSettingsContext(); return (
@@ -47,6 +49,7 @@ export const BrainCatalogue = ({ }`} > {brain.integration_name} { const { currentBrain, setCurrentBrainId } = useBrainContext(); - + const { isDarkMode } = useUserSettingsContext(); const removeCurrentBrain = (): void => { setCurrentBrainId(null); }; @@ -29,16 +30,17 @@ export const CurrentBrain = ({
Talking to
- {currentBrain.integration_logo_url ? ( - brain - ) : ( - - )} + logo_image {currentBrain.name}
diff --git a/frontend/lib/components/Menu/Menu.module.scss b/frontend/lib/components/Menu/Menu.module.scss index 3b9df4075..230775802 100644 --- a/frontend/lib/components/Menu/Menu.module.scss +++ b/frontend/lib/components/Menu/Menu.module.scss @@ -1,10 +1,9 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Spacings.module.scss"; @use "@/styles/ZIndexes.module.scss"; .menu_container { - background-color: Colors.$highlight; - border-right: 1px solid Colors.$lightest-black; + background-color: var(--background-1); + border-right: 1px solid var(--border-1); .menu_wrapper { padding-top: Spacings.$spacing05; @@ -37,7 +36,7 @@ .social_buttons_wrapper { padding-block: Spacings.$spacing04; - border-top: 1px solid Colors.$lightest-black; + border-top: 1px solid var(--border-1); } } } diff --git a/frontend/lib/components/Menu/Menu.tsx b/frontend/lib/components/Menu/Menu.tsx index 3a37b4288..5e6283e06 100644 --- a/frontend/lib/components/Menu/Menu.tsx +++ b/frontend/lib/components/Menu/Menu.tsx @@ -7,6 +7,7 @@ import { useChatsList } from "@/app/chat/[chatId]/hooks/useChatsList"; import { QuivrLogo } from "@/lib/assets/QuivrLogo"; import { nonProtectedPaths } from "@/lib/config/routesConfig"; import { useMenuContext } from "@/lib/context/MenuProvider/hooks/useMenuContext"; +import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; import styles from "./Menu.module.scss"; import { AnimatedDiv } from "./components/AnimationDiv"; @@ -23,6 +24,7 @@ export const Menu = (): JSX.Element => { const router = useRouter(); const pathname = usePathname() ?? ""; const [isLogoHovered, setIsLogoHovered] = useState(false); + const { isDarkMode } = useUserSettingsContext(); useChatsList(); @@ -53,7 +55,9 @@ export const Menu = (): JSX.Element => { >
diff --git a/frontend/lib/components/Menu/components/DiscussionButton/DiscussionButton.module.scss b/frontend/lib/components/Menu/components/DiscussionButton/DiscussionButton.module.scss index 4023d7398..3f2c57ff4 100644 --- a/frontend/lib/components/Menu/components/DiscussionButton/DiscussionButton.module.scss +++ b/frontend/lib/components/Menu/components/DiscussionButton/DiscussionButton.module.scss @@ -1,4 +1,4 @@ -@use "@/styles/Colors.module.scss"; +@use "@/styles/BoxShadow.module.scss"; @use "@/styles/IconSizes.module.scss"; @use "@/styles/Radius.module.scss"; @use "@/styles/Spacings.module.scss"; @@ -8,11 +8,11 @@ padding: Spacings.$spacing03; justify-content: space-between; align-items: center; - border: 1px solid Colors.$lighter-grey; + border: 1px solid var(--border-0); border-radius: Radius.$big; - background-color: Colors.$white; + background-color: var(--background-0); cursor: pointer; - color: Colors.$dark-grey; + color: var(--text-2); transition: box-shadow 0.3s ease; .left_wrapper { @@ -30,7 +30,7 @@ align-items: center; justify-content: center; font-size: 12px; - border: 1px solid Colors.$lighter-grey; + border: 1px solid var(--border-0); border-radius: Radius.$small; width: 16px; height: 16px; @@ -39,7 +39,7 @@ } &:hover { - border-color: Colors.$primary; - box-shadow: 0 0 0 1px Colors.$primary; + border-color: var(--primary-0); + box-shadow: BoxShadow.$primary; } } diff --git a/frontend/lib/components/Menu/components/MenuButton/MenuButton.module.scss b/frontend/lib/components/Menu/components/MenuButton/MenuButton.module.scss index 2d806831b..c7d1a702c 100644 --- a/frontend/lib/components/Menu/components/MenuButton/MenuButton.module.scss +++ b/frontend/lib/components/Menu/components/MenuButton/MenuButton.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Radius.module.scss"; @use "@/styles/Spacings.module.scss"; @use "@/styles/Typography.module.scss"; @@ -14,7 +13,7 @@ border-left: 2px solid transparent; &.selected { - border-left: 2px solid Colors.$primary; + border-left: 2px solid var(--primary-0); border-radius: 0 Radius.$normal Radius.$normal 0; } @@ -27,19 +26,19 @@ .title { @include Typography.EllipsisOverflow; font-size: Typography.$small; - color: Colors.$black; + color: var(--text-3); &.gold { - color: Colors.$gold; + color: var(--gold); } &.primary { - color: Colors.$primary; + color: var(--primary-0); } } } &:hover { - background-color: Colors.$lightest-black; + background-color: var(--background-3); } } diff --git a/frontend/lib/components/Menu/components/ThreadsButton/ThreadsButton.module.scss b/frontend/lib/components/Menu/components/ThreadsButton/ThreadsButton.module.scss index 46704613f..51f6d05e7 100644 --- a/frontend/lib/components/Menu/components/ThreadsButton/ThreadsButton.module.scss +++ b/frontend/lib/components/Menu/components/ThreadsButton/ThreadsButton.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/ScreenSizes.module.scss"; @use "@/styles/Spacings.module.scss"; @use "@/styles/Typography.module.scss"; @@ -7,7 +6,7 @@ position: relative; padding: Spacings.$spacing03; padding-top: 0; - color: Colors.$dark-grey; + color: var(--text-2); font-size: Typography.$small; max-height: 300px; overflow-y: scroll; diff --git a/frontend/lib/components/Menu/components/ThreadsButton/ThreadsSection/ThreadItem/ThreadItem.module.scss b/frontend/lib/components/Menu/components/ThreadsButton/ThreadsSection/ThreadItem/ThreadItem.module.scss index 2d2b0570d..b157c1625 100644 --- a/frontend/lib/components/Menu/components/ThreadsButton/ThreadsSection/ThreadItem/ThreadItem.module.scss +++ b/frontend/lib/components/Menu/components/ThreadsButton/ThreadsSection/ThreadItem/ThreadItem.module.scss @@ -1,11 +1,10 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Radius.module.scss"; @use "@/styles/Spacings.module.scss"; @use "@/styles/Typography.module.scss"; @use "@/styles/ZIndexes.module.scss"; .thread_item_wrapper { - color: Colors.$black; + color: var(--text-3); display: flex; justify-content: space-between; gap: Spacings.$spacing03; @@ -15,13 +14,13 @@ .edit_thread_name { @include Typography.EllipsisOverflow; - color: Colors.$black; + color: var(--text-3); border: none; height: 21px; outline: none; padding: 0; font-size: Typography.$small; - background-color: Colors.$lighter-grey; + background-color: var(--background-3); border-radius: Radius.$small; &:focus { @@ -33,7 +32,7 @@ @include Typography.EllipsisOverflow; &:hover { - color: Colors.$primary; + color: var(--primary-0); } } diff --git a/frontend/lib/components/Menu/components/ThreadsButton/ThreadsSection/ThreadsSection.module.scss b/frontend/lib/components/Menu/components/ThreadsButton/ThreadsSection/ThreadsSection.module.scss index c0c8881c7..22be94915 100644 --- a/frontend/lib/components/Menu/components/ThreadsButton/ThreadsSection/ThreadsSection.module.scss +++ b/frontend/lib/components/Menu/components/ThreadsButton/ThreadsSection/ThreadsSection.module.scss @@ -1,8 +1,7 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Spacings.module.scss"; .chats_wrapper { - border-left: 1px solid Colors.$normal-grey; + border-left: 1px solid var(--border-2); padding-left: Spacings.$spacing03; margin: Spacings.$spacing02; display: flex; diff --git a/frontend/lib/components/PageHeader/PageHeader.module.scss b/frontend/lib/components/PageHeader/PageHeader.module.scss index fffd5ef9a..c85f915a7 100644 --- a/frontend/lib/components/PageHeader/PageHeader.module.scss +++ b/frontend/lib/components/PageHeader/PageHeader.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/ScreenSizes.module.scss"; @use "@/styles/Spacings.module.scss"; @use "@/styles/Typography.module.scss"; @@ -9,7 +8,7 @@ justify-content: space-between; padding: Spacings.$spacing04; padding-left: Spacings.$spacing09; - border-bottom: 1px solid Colors.$lightest-black; + border-bottom: 1px solid var(--border-1); .left { @include Typography.H2; @@ -32,5 +31,6 @@ display: flex; gap: Spacings.$spacing03; align-self: flex-end; + align-items: center; } } diff --git a/frontend/lib/components/PageHeader/PageHeader.tsx b/frontend/lib/components/PageHeader/PageHeader.tsx index fe78447a7..953ce5b08 100644 --- a/frontend/lib/components/PageHeader/PageHeader.tsx +++ b/frontend/lib/components/PageHeader/PageHeader.tsx @@ -1,4 +1,7 @@ +import { useEffect, useState } from "react"; + import { useMenuContext } from "@/lib/context/MenuProvider/hooks/useMenuContext"; +import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; import { ButtonType } from "@/lib/types/QuivrButton"; import styles from "./PageHeader.module.scss"; @@ -18,6 +21,16 @@ export const PageHeader = ({ buttons, }: Props): JSX.Element => { const { isOpened } = useMenuContext(); + const { isDarkMode, setIsDarkMode } = useUserSettingsContext(); + const [lightModeIconName, setLightModeIconName] = useState("sun"); + + const toggleTheme = () => { + setIsDarkMode(!isDarkMode); + }; + + useEffect(() => { + setLightModeIconName(isDarkMode ? "sun" : "moon"); + }, [isDarkMode]); return (
@@ -36,6 +49,13 @@ export const PageHeader = ({ hidden={button.hidden} /> ))} +
); diff --git a/frontend/lib/components/SearchModal/SearchModal.module.scss b/frontend/lib/components/SearchModal/SearchModal.module.scss index 6cc5902c8..cb9f61f57 100644 --- a/frontend/lib/components/SearchModal/SearchModal.module.scss +++ b/frontend/lib/components/SearchModal/SearchModal.module.scss @@ -1,11 +1,10 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/ScreenSizes.module.scss"; @use "@/styles/Spacings.module.scss"; @use "@/styles/ZIndexes.module.scss"; .search_modal_wrapper { display: flex; - background-color: rgba(Colors.$dark-black, 0.94); + background-color: var(--background-blur); height: 100%; width: 100%; position: absolute; diff --git a/frontend/lib/components/Stripe/PricingModal/StripePricingModal.tsx b/frontend/lib/components/Stripe/PricingModal/StripePricingModal.tsx index 84930d1f3..17da65c15 100644 --- a/frontend/lib/components/Stripe/PricingModal/StripePricingModal.tsx +++ b/frontend/lib/components/Stripe/PricingModal/StripePricingModal.tsx @@ -1,6 +1,6 @@ import { StripePricingTable } from "./components/PricingTable/PricingTable"; -import { Modal } from "../../ui/ModalPayment"; +import { Modal } from "../../ui/Modal/Modal"; type StripePricingModalProps = { Trigger: JSX.Element; @@ -10,7 +10,7 @@ export const StripePricingModal = ({ Trigger, }: StripePricingModalProps): JSX.Element => { return ( - }> + } unforceWhite={true}> ); diff --git a/frontend/lib/components/Stripe/PricingModal/components/PricingTable/PricingTable.module.scss b/frontend/lib/components/Stripe/PricingModal/components/PricingTable/PricingTable.module.scss new file mode 100644 index 000000000..af8e17b16 --- /dev/null +++ b/frontend/lib/components/Stripe/PricingModal/components/PricingTable/PricingTable.module.scss @@ -0,0 +1,9 @@ +@use "@/styles/Spacings.module.scss"; + +.info_content { + padding: Spacings.$spacing06; + + .bold { + font-weight: 800; + } +} diff --git a/frontend/lib/components/Stripe/PricingModal/components/PricingTable/PricingTable.tsx b/frontend/lib/components/Stripe/PricingModal/components/PricingTable/PricingTable.tsx index 415498faf..76ac17d2e 100644 --- a/frontend/lib/components/Stripe/PricingModal/components/PricingTable/PricingTable.tsx +++ b/frontend/lib/components/Stripe/PricingModal/components/PricingTable/PricingTable.tsx @@ -1,38 +1,25 @@ +import { MessageInfoBox } from "@/lib/components/ui/MessageInfoBox/MessageInfoBox"; + +import styles from "./PricingTable.module.scss"; + const PRICING_TABLE_ID = process.env.NEXT_PUBLIC_STRIPE_PRICING_TABLE_ID; const PUBLISHABLE_KEY = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY; export const StripePricingTable = (): JSX.Element => { return ( <> -
-
-

Free Tier

-
    -
  • 🧠 3 brains
  • -
  • - 🙋‍♂️ 100 questions per month -
  • -
  • - 💾 Up to 30Mb of storage -
  • -
-
-
-

- Premium Features -

-
    -
  • - 🧠 Bigger & more Brains -
  • -
  • - 🙋‍♂️ More credits & access to premium models (GPT4, Mistral) -
  • -
  • - 🚀 GPT3.5 = 1 credit & GPT4 = 20 credits -
  • -
-
+
+ +
+ {"The free tier allows you to have"} + 3 brains + {"and"} + 100 chat credits + { + "per month. You can upgrade to unlock more brains, more chat credits and access to premium models." + } +
+
diff --git a/frontend/lib/components/Stripe/PricingModal/components/PricingTable/types/types.d.ts b/frontend/lib/components/Stripe/PricingModal/components/PricingTable/types/types.d.ts new file mode 100644 index 000000000..4da4688a0 --- /dev/null +++ b/frontend/lib/components/Stripe/PricingModal/components/PricingTable/types/types.d.ts @@ -0,0 +1,12 @@ +import * as React from "react"; + +declare global { + namespace JSX { + interface IntrinsicElements { + "stripe-pricing-table": React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + >; + } + } +} diff --git a/frontend/lib/components/UploadDocumentModal/UploadDocumentModal.module.scss b/frontend/lib/components/UploadDocumentModal/UploadDocumentModal.module.scss index 78da62c2e..a9f279cd5 100644 --- a/frontend/lib/components/UploadDocumentModal/UploadDocumentModal.module.scss +++ b/frontend/lib/components/UploadDocumentModal/UploadDocumentModal.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Spacings.module.scss"; @use "@/styles/ZIndexes.module.scss"; @@ -7,7 +6,7 @@ display: flex; flex-direction: column; justify-content: space-between; - background-color: Colors.$white; + background-color: var(--background-0); width: 100%; flex: 1; diff --git a/frontend/lib/components/ui/CountrySelector/CountrySelector.module.scss b/frontend/lib/components/ui/CountrySelector/CountrySelector.module.scss index d89d6a657..180c23df4 100644 --- a/frontend/lib/components/ui/CountrySelector/CountrySelector.module.scss +++ b/frontend/lib/components/ui/CountrySelector/CountrySelector.module.scss @@ -1,17 +1,17 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Radius.module.scss"; .selection { border-radius: Radius.$normal; box-shadow: none; cursor: pointer; + background-color: var(--background-0); &:hover { - background-color: Colors.$lightest-black; + background-color: var(--background-3); } &:focus { box-shadow: none; - border-color: Colors.$primary; + border-color: var(--primary-0); } } diff --git a/frontend/lib/components/ui/FoldableSection/FoldableSection.module.scss b/frontend/lib/components/ui/FoldableSection/FoldableSection.module.scss index 140335b95..c987eba02 100644 --- a/frontend/lib/components/ui/FoldableSection/FoldableSection.module.scss +++ b/frontend/lib/components/ui/FoldableSection/FoldableSection.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Radius.module.scss"; @use "@/styles/Spacings.module.scss"; @use "@/styles/Typography.module.scss"; @@ -8,7 +7,7 @@ display: flex; flex-direction: column; border-radius: Radius.$normal; - border: 1px dashed Colors.$lighter-grey; + border: 1px dashed var(--border-0); overflow: hidden; font-size: Typography.$small; @@ -16,7 +15,7 @@ overflow: hidden; transition: max-height 0.3s Transitions.$easeOutBack; } - + .contentCollapsed { max-height: 0; } @@ -44,10 +43,10 @@ } &:hover { - background-color: Colors.$lightest-black; + background-color: var(--background-3); } } - + .iconRotate { transition: transform 0.3s Transitions.$easeOutBack; } diff --git a/frontend/lib/components/ui/Icon/Icon.module.scss b/frontend/lib/components/ui/Icon/Icon.module.scss index c9243bf24..8bd075a66 100644 --- a/frontend/lib/components/ui/Icon/Icon.module.scss +++ b/frontend/lib/components/ui/Icon/Icon.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/IconSizes.module.scss"; .small { @@ -30,55 +29,55 @@ } .black { - color: Colors.$black; + color: var(--icon-3); &.hovered { - color: Colors.$primary; + color: var(--primary-0); } } .dark-grey { - color: Colors.$dark-grey; + color: var(--icon-2); } .grey { - color: Colors.$normal-grey; + color: var(--icon-1); } .primary { - color: Colors.$primary; + color: var(--primary-0); } .accent { - color: Colors.$accent; + color: var(--accent); } .gold { - color: Colors.$gold; + color: var(--gold); } .white { - color: Colors.$white; + color: var(--icon-0); } .dangerous { - color: Colors.$dangerous; + color: var(--dangerous); &.hovered { - color: Colors.$dangerous-dark; + color: var(--dangerous-dark); } } .warning { - color: Colors.$warning; + color: var(--warning); } .success { - color: Colors.$success; + color: var(--success); } .disabled { - color: Colors.$black; + color: var(--icon-3); pointer-events: none; opacity: 0.2; } diff --git a/frontend/lib/components/ui/LoaderIcon/LoaderIcon.module.scss b/frontend/lib/components/ui/LoaderIcon/LoaderIcon.module.scss index 286aa0afa..751016d1a 100644 --- a/frontend/lib/components/ui/LoaderIcon/LoaderIcon.module.scss +++ b/frontend/lib/components/ui/LoaderIcon/LoaderIcon.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/IconSizes.module.scss"; .loader_icon { diff --git a/frontend/lib/components/ui/MessageInfoBox/MessageInfoBox.module.scss b/frontend/lib/components/ui/MessageInfoBox/MessageInfoBox.module.scss index 076c6f066..9c25f93da 100644 --- a/frontend/lib/components/ui/MessageInfoBox/MessageInfoBox.module.scss +++ b/frontend/lib/components/ui/MessageInfoBox/MessageInfoBox.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Radius.module.scss"; @use "@/styles/Spacings.module.scss"; @@ -8,25 +7,29 @@ align-items: center; gap: Spacings.$spacing03; width: fit-content; - border: 1px solid Colors.$normal-grey; - color: Colors.$black; + border: 1px solid var(--border-2); + color: var(--text-3); border-radius: Radius.$normal; &.success { - border-color: Colors.$success; - color: Colors.$success; - background-color: Colors.$success-lightest; + border-color: var(--success); + color: var(--success); + background-color: var(--success-lightest); } &.info { - border-color: Colors.$primary; - color: Colors.$primary; - background-color: Colors.$primary-lightest; + border-color: var(--primary-0); + color: var(--primary-0); + background-color: var(--primary-2); } &.warning { - border-color: Colors.$warning; - color: Colors.$warning; - background-color: Colors.$warning-lightest; + border-color: var(--warning); + color: var(--warning); + background-color: var(--warning-lightest); + } + + &.dark { + background-color: var(--background-special-0); } } diff --git a/frontend/lib/components/ui/MessageInfoBox/MessageInfoBox.tsx b/frontend/lib/components/ui/MessageInfoBox/MessageInfoBox.tsx index e8e7edd02..5391327bf 100644 --- a/frontend/lib/components/ui/MessageInfoBox/MessageInfoBox.tsx +++ b/frontend/lib/components/ui/MessageInfoBox/MessageInfoBox.tsx @@ -1,3 +1,4 @@ +import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; import { iconList } from "@/lib/helpers/iconList"; import { Color } from "@/lib/types/Colors"; @@ -8,11 +9,13 @@ import { Icon } from "../Icon/Icon"; export type MessageInfoBoxProps = { children: React.ReactNode; type: "info" | "success" | "warning" | "error"; + unforceWhite?: boolean; }; export const MessageInfoBox = ({ children, type, + unforceWhite, }: MessageInfoBoxProps): JSX.Element => { const getIconProps = (): { iconName: keyof typeof iconList; @@ -30,8 +33,14 @@ export const MessageInfoBox = ({ } }; + const { isDarkMode } = useUserSettingsContext(); + return ( -
+
{ const [isOpen, setOpen] = useState(false); const { t } = useTranslation(["translation"]); @@ -64,7 +66,7 @@ export const Modal = ({ & { - isOpen: boolean; - setOpen: (isOpen: boolean) => void; - }); - -export const Modal = ({ - title, - desc, - children, - Trigger, - CloseTrigger, - isOpen: customIsOpen, - setOpen: customSetOpen, -}: ModalProps): JSX.Element => { - const [isOpen, setOpen] = useState(false); - const { t } = useTranslation(["translation"]); - - return ( - - {Trigger !== undefined && ( - {Trigger} - )} - - {customIsOpen ?? isOpen ? ( - - - - - - - {title} - - - {desc} - - {children} - - {CloseTrigger !== undefined ? ( - CloseTrigger - ) : ( - - )} - - - - - - - - - - ) : null} - - - ); -}; diff --git a/frontend/lib/components/ui/OptionsModal/OptionsModal.module.scss b/frontend/lib/components/ui/OptionsModal/OptionsModal.module.scss index db05f1991..ad2722067 100644 --- a/frontend/lib/components/ui/OptionsModal/OptionsModal.module.scss +++ b/frontend/lib/components/ui/OptionsModal/OptionsModal.module.scss @@ -1,13 +1,13 @@ -@use "@/styles/Colors.module.scss"; +@use "@/styles/BoxShadow.module.scss"; @use "@/styles/Radius.module.scss"; @use "@/styles/Spacings.module.scss"; @use "@/styles/ZIndexes.module.scss"; .options_modal_wrapper { - background-color: Colors.$highlight; + background-color: var(--background-1); border-radius: Radius.$normal; border-radius: Radius.$normal; - box-shadow: 0 1px 2px rgb(0, 0, 0, 0.25); + box-shadow: BoxShadow.$small; width: fit-content; .option { @@ -21,11 +21,11 @@ overflow: hidden; &:not(:first-child) { - border-top: 1px solid Colors.$light-grey; + border-top: 1px solid var(--border-2); } &:hover { - background-color: Colors.$primary-lightest; + background-color: var(--background-special-0); } &.disabled { diff --git a/frontend/lib/components/ui/QuivrButton/QuivrButton.module.scss b/frontend/lib/components/ui/QuivrButton/QuivrButton.module.scss index 1bf5323a6..97aa2cf85 100644 --- a/frontend/lib/components/ui/QuivrButton/QuivrButton.module.scss +++ b/frontend/lib/components/ui/QuivrButton/QuivrButton.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Radius.module.scss"; @use "@/styles/ScreenSizes.module.scss"; @use "@/styles/Spacings.module.scss"; @@ -14,46 +13,50 @@ cursor: pointer; display: flex; width: fit-content; - background-color: Colors.$white; + background-color: var(--background-0); &.hidden { display: none; } &.primary { - border-color: Colors.$primary; - color: Colors.$primary; + border-color: var(--primary-0); + color: var(--primary-0); &:hover { - background-color: Colors.$primary; - color: Colors.$white; + background-color: var(--primary-0); + color: var(--text-0); } } &.dangerous { - border-color: Colors.$dangerous; - color: Colors.$dangerous; + border-color: var(--dangerous); + color: var(--dangerous); &:hover { - background-color: Colors.$dangerous; - color: Colors.$white; + background-color: var(--dangerous); + color: var(--text-0); } } &.gold { - border-color: Colors.$gold; - color: Colors.$gold; + border-color: var(--gold); + color: var(--gold); &:hover { - background-color: Colors.$gold; - color: Colors.$white; + background-color: var(--gold); + color: var(--text-0); } } &.disabled { - border-color: Colors.$normal-grey; + border-color: var(--border-2); pointer-events: none; - color: Colors.$normal-grey; + color: var(--text-1); + + &.dark { + opacity: 0.2; + } } } diff --git a/frontend/lib/components/ui/QuivrButton/QuivrButton.tsx b/frontend/lib/components/ui/QuivrButton/QuivrButton.tsx index 3720cf226..4ed43da26 100644 --- a/frontend/lib/components/ui/QuivrButton/QuivrButton.tsx +++ b/frontend/lib/components/ui/QuivrButton/QuivrButton.tsx @@ -1,5 +1,6 @@ import { useState } from "react"; +import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; import { ButtonType } from "@/lib/types/QuivrButton"; import styles from "./QuivrButton.module.scss"; @@ -17,6 +18,7 @@ export const QuivrButton = ({ hidden, }: ButtonType): JSX.Element => { const [hovered, setHovered] = useState(false); + const { isDarkMode } = useUserSettingsContext(); return (
onClick?.()} + // eslint-disable-next-line @typescript-eslint/no-misused-promises, @typescript-eslint/prefer-optional-chain, @typescript-eslint/no-unnecessary-condition + onClick={() => onClick && onClick()} onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)} > diff --git a/frontend/lib/components/ui/SearchBar/SearchBar.module.scss b/frontend/lib/components/ui/SearchBar/SearchBar.module.scss index cb361ecef..88d98bee8 100644 --- a/frontend/lib/components/ui/SearchBar/SearchBar.module.scss +++ b/frontend/lib/components/ui/SearchBar/SearchBar.module.scss @@ -1,4 +1,4 @@ -@use "@/styles/Colors.module.scss"; +@use "@/styles/BoxShadow.module.scss"; @use "@/styles/IconSizes.module.scss"; @use "@/styles/Radius.module.scss"; @use "@/styles/Spacings.module.scss"; @@ -7,11 +7,11 @@ display: flex; flex-direction: column; gap: Spacings.$spacing03; - background-color: Colors.$white; + background-color: var(--background-0); border-radius: Radius.$big; - border: 1px solid Colors.$lighter-grey; + border: 1px solid var(--border-0); overflow: hidden; - box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.25); + box-shadow: BoxShadow.$large; .editor_wrapper { display: flex; @@ -26,11 +26,11 @@ .search_icon { width: IconSizes.$big; height: IconSizes.$big; - color: Colors.$accent; + color: var(--accent); cursor: pointer; &.disabled { - color: Colors.$black; + color: var(--text-3); pointer-events: none; opacity: 0.2; } diff --git a/frontend/lib/components/ui/SingleSelector/SingleSelector.module.scss b/frontend/lib/components/ui/SingleSelector/SingleSelector.module.scss index 3c86980a0..3358e56e9 100644 --- a/frontend/lib/components/ui/SingleSelector/SingleSelector.module.scss +++ b/frontend/lib/components/ui/SingleSelector/SingleSelector.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/IconSizes.module.scss"; @use "@/styles/Radius.module.scss"; @use "@/styles/Spacings.module.scss"; @@ -9,12 +8,12 @@ display: flex; flex-direction: column; position: relative; - background-color: Colors.$white; + background-color: var(--background-0); .first_line_wrapper { display: flex; justify-content: space-between; - border: 1px solid Colors.$normal-grey; + border: 1px solid var(--border-2); border-radius: Radius.$big; align-items: center; cursor: pointer; @@ -37,14 +36,14 @@ .label { @include Typography.EllipsisOverflow; - background-color: Colors.$primary-light; + background-color: var(--background-special-1); border-radius: Radius.$normal; padding-inline: Spacings.$spacing05; padding-block: Spacings.$spacing02; white-space: nowrap; &.not_set { - color: Colors.$normal-grey; + color: var(--text-1); background-color: transparent; padding-inline: 0; } @@ -67,10 +66,10 @@ .options { position: absolute; - background-color: Colors.$white; + background-color: var(--background-0); width: 100%; top: 100%; - border: 1px solid Colors.$normal-grey; + border: 1px solid var(--border-2); border-top: none; border-radius: 0 0 Radius.$big Radius.$big; overflow: hidden; @@ -88,7 +87,7 @@ &:hover { .option_name { - background-color: Colors.$primary-lightest; + background-color: var(--background-special-1); } } @@ -98,7 +97,7 @@ .option_name { @include Typography.EllipsisOverflow; - border: 1px solid Colors.$lightest-black; + border: 1px solid var(--border-1); border-radius: Radius.$small; padding-inline: Spacings.$spacing05; padding-block: Spacings.$spacing02; diff --git a/frontend/lib/components/ui/Tabs/Tabs.module.scss b/frontend/lib/components/ui/Tabs/Tabs.module.scss index 760d048e2..98a32474d 100644 --- a/frontend/lib/components/ui/Tabs/Tabs.module.scss +++ b/frontend/lib/components/ui/Tabs/Tabs.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Radius.module.scss"; @use "@/styles/ScreenSizes.module.scss"; @use "@/styles/Spacings.module.scss"; @@ -12,25 +11,25 @@ align-items: center; justify-content: center; flex: 1; - border-bottom: 2px solid Colors.$lightest-grey; + border-bottom: 2px solid var(--border-0); padding-block: Spacings.$spacing03; cursor: pointer; box-sizing: border-box; gap: Spacings.$spacing03; &.selected { - border-bottom-color: Colors.$primary; - color: Colors.$primary; - background-color: Colors.$lightest-grey; + border-bottom-color: var(--primary-0); + color: var(--primary-0); + background-color: var(--background-2); } &:hover { - color: Colors.$primary; + color: var(--primary-0); } &.disabled { pointer-events: none; - color: Colors.$normal-grey; + color: var(--text-1); } @media (max-width: ScreenSizes.$small) { diff --git a/frontend/lib/components/ui/Tag/Tag.module.scss b/frontend/lib/components/ui/Tag/Tag.module.scss index 052eeb9dc..0d8a15fef 100644 --- a/frontend/lib/components/ui/Tag/Tag.module.scss +++ b/frontend/lib/components/ui/Tag/Tag.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Radius.module.scss"; @use "@/styles/Spacings.module.scss"; @use "@/styles/Typography.module.scss"; @@ -11,10 +10,10 @@ font-size: Typography.$tiny; &.primary { - background-color: Colors.$primary-light; + background-color: var(--primary-1); } &.gold { - background-color: Colors.$gold; + background-color: var(--gold); } } diff --git a/frontend/lib/components/ui/TextAreaInput/TextAreaInput.module.scss b/frontend/lib/components/ui/TextAreaInput/TextAreaInput.module.scss index 806d152f3..cb34fbcc7 100644 --- a/frontend/lib/components/ui/TextAreaInput/TextAreaInput.module.scss +++ b/frontend/lib/components/ui/TextAreaInput/TextAreaInput.module.scss @@ -1,17 +1,16 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Radius.module.scss"; @use "@/styles/Spacings.module.scss"; .text_area_input_container { display: flex; - border: 1px solid Colors.$lighter-grey; + border: 1px solid var(--border-0); gap: Spacings.$spacing03; padding-block: Spacings.$spacing02; padding-inline: Spacings.$spacing03; border-radius: Radius.$big; align-items: center; width: 100%; - background-color: Colors.$white; + background-color: var(--background-0); &.simple { border: none; @@ -30,10 +29,11 @@ } .text_area_input { - caret-color: Colors.$accent; + caret-color: var(--accent); border: none; flex: 1; resize: none; + background-color: transparent; &:focus { box-shadow: none; diff --git a/frontend/lib/components/ui/TextButton/TextButton.module.scss b/frontend/lib/components/ui/TextButton/TextButton.module.scss index cc9deedb4..e63539b18 100644 --- a/frontend/lib/components/ui/TextButton/TextButton.module.scss +++ b/frontend/lib/components/ui/TextButton/TextButton.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Spacings.module.scss"; .text_button_wrapper { @@ -10,17 +9,17 @@ } .black { - color: Colors.$black; + color: var(--text-3); &.hovered { - color: Colors.$primary; + color: var(--primary-0); } } .dangerous { - color: Colors.$dangerous; + color: var(--dangerous); &.hovered { - color: Colors.$dangerous-dark; + color: var(--dangerous)-dark; } } diff --git a/frontend/lib/components/ui/TextInput/TextInput.module.scss b/frontend/lib/components/ui/TextInput/TextInput.module.scss index 8bc667bfd..59b969656 100644 --- a/frontend/lib/components/ui/TextInput/TextInput.module.scss +++ b/frontend/lib/components/ui/TextInput/TextInput.module.scss @@ -1,17 +1,16 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Radius.module.scss"; @use "@/styles/Spacings.module.scss"; .text_input_container { display: flex; - border: 1px solid Colors.$lighter-grey; + border: 1px solid var(--border-0); gap: Spacings.$spacing03; padding-block: Spacings.$spacing02; padding-inline: Spacings.$spacing03; border-radius: Radius.$big; align-items: center; width: 100%; - background-color: Colors.$white; + background-color: var(--background-0); &.simple { border: none; @@ -30,7 +29,7 @@ } .text_input { - caret-color: Colors.$accent; + caret-color: var(--accent); border: none; flex: 1; background-color: transparent; diff --git a/frontend/lib/components/ui/Tooltip/Tooltip.module.scss b/frontend/lib/components/ui/Tooltip/Tooltip.module.scss index de95790f2..af48defa9 100644 --- a/frontend/lib/components/ui/Tooltip/Tooltip.module.scss +++ b/frontend/lib/components/ui/Tooltip/Tooltip.module.scss @@ -1,4 +1,3 @@ -@use "@/styles/Colors.module.scss"; @use "@/styles/Radius.module.scss"; @use "@/styles/Spacings.module.scss"; @use "@/styles/Typography.module.scss"; @@ -6,7 +5,7 @@ .tooltip_content_wrapper { z-index: ZIndexes.$tooltip; - background-color: Colors.$lightest-black; + background-color: var(--background-3); padding: Spacings.$spacing03; border-radius: Radius.$normal; font-size: Typography.$small; diff --git a/frontend/lib/context/UserSettingsProvider/User-settings.provider.tsx b/frontend/lib/context/UserSettingsProvider/User-settings.provider.tsx new file mode 100644 index 000000000..d0798f154 --- /dev/null +++ b/frontend/lib/context/UserSettingsProvider/User-settings.provider.tsx @@ -0,0 +1,76 @@ +import { createContext, useEffect, useState } from "react"; + +import { parseBoolean } from "@/lib/helpers/parseBoolean"; + +type UserSettingsContextType = { + isDarkMode: boolean; + setIsDarkMode: React.Dispatch>; +}; + +export const UserSettingsContext = createContext< + UserSettingsContextType | undefined +>(undefined); + +export const UserSettingsProvider = ({ + children, +}: { + children: React.ReactNode; +}): JSX.Element => { + const [isDarkMode, setIsDarkMode] = useState(() => { + if (typeof window !== "undefined") { + const localIsDarkMode = localStorage.getItem("isDarkMode"); + + return parseBoolean(localIsDarkMode); + } + + return false; + }); + + useEffect(() => { + if (typeof window !== "undefined") { + const prefersDarkMode = window.matchMedia( + "(prefers-color-scheme: dark)" + ).matches; + const localIsDarkMode = localStorage.getItem("isDarkMode"); + const newState = + localIsDarkMode !== null + ? parseBoolean(localIsDarkMode) + : prefersDarkMode; + setIsDarkMode(newState); + newState + ? document.body.classList.add("dark_mode") + : document.body.classList.remove("dark_mode"); + + const mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)"); + const listener = (event: MediaQueryListEvent) => { + const updatedState = event.matches; + setIsDarkMode(updatedState); + localStorage.setItem("isDarkMode", JSON.stringify(updatedState)); + }; + mediaQueryList.addEventListener("change", listener); + + return () => { + mediaQueryList.removeEventListener("change", listener); + }; + } + }, []); + + useEffect(() => { + isDarkMode + ? document.body.classList.add("dark_mode") + : document.body.classList.remove("dark_mode"); + + localStorage.setItem("isDarkMode", JSON.stringify(isDarkMode)); + }, [isDarkMode]); + + return ( + + {children} + + ); +}; diff --git a/frontend/lib/context/UserSettingsProvider/hooks/useUserSettingsContext.tsx b/frontend/lib/context/UserSettingsProvider/hooks/useUserSettingsContext.tsx new file mode 100644 index 000000000..bf1386917 --- /dev/null +++ b/frontend/lib/context/UserSettingsProvider/hooks/useUserSettingsContext.tsx @@ -0,0 +1,15 @@ +import { useContext } from "react"; + +import { UserSettingsContext } from "../User-settings.provider"; + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const useUserSettingsContext = () => { + const context = useContext(UserSettingsContext); + if (context === undefined) { + throw new Error( + "useUserSettingsContext must be used within a UserSettingsProvider" + ); + } + + return context; +}; diff --git a/frontend/lib/helpers/iconList.ts b/frontend/lib/helpers/iconList.ts index be706fee0..85581b796 100644 --- a/frontend/lib/helpers/iconList.ts +++ b/frontend/lib/helpers/iconList.ts @@ -14,10 +14,12 @@ import { FaGithub, FaKey, FaLinkedin, + FaMoon, FaRegFileAlt, FaRegKeyboard, FaRegStar, FaRegUserCircle, + FaSun, FaTwitter, FaUnlock, } from "react-icons/fa"; @@ -100,6 +102,7 @@ export const iconList: { [name: string]: IconType } = { linkedin: FaLinkedin, loader: AiOutlineLoading3Quarters, logout: IoMdLogOut, + moon: FaMoon, options: SlOptions, paragraph: BsTextParagraph, prompt: FaRegKeyboard, @@ -110,6 +113,7 @@ export const iconList: { [name: string]: IconType } = { share: IoShareSocial, software: CgSoftwareDownload, star: FaRegStar, + sun: FaSun, twitter: FaTwitter, unlock: FaUnlock, upload: FiUpload, diff --git a/frontend/lib/helpers/parseBoolean.ts b/frontend/lib/helpers/parseBoolean.ts new file mode 100644 index 000000000..e76ca877c --- /dev/null +++ b/frontend/lib/helpers/parseBoolean.ts @@ -0,0 +1,7 @@ +export const parseBoolean = (value: string | null): boolean => { + if (value === null) { + return false; + } + + return value.toLowerCase() === "true"; +}; diff --git a/frontend/public/default_brain_image.png b/frontend/public/default_brain_image.png new file mode 100644 index 0000000000000000000000000000000000000000..9f682cdef646afb4c6762acc9234772c406a927e GIT binary patch literal 5259 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