From 9fef36eae33bae9d0c133a206fa4a27526184f8a Mon Sep 17 00:00:00 2001 From: phntxx Date: Sun, 21 Mar 2021 18:05:24 +0100 Subject: [PATCH] Refactor Part 1 --- src/app.tsx | 23 +-- src/components/app.tsx | 7 +- src/components/bookmarkGroup.tsx | 2 +- src/components/elements.tsx | 13 +- src/components/fetch.tsx | 193 ------------------ src/components/greeter.tsx | 2 +- src/components/icon.tsx | 26 ++- src/components/imprint.tsx | 9 +- src/components/modal.tsx | 33 ++- src/components/searchBar.tsx | 2 +- src/components/settings.tsx | 138 ++++++++----- src/{components => }/data/apps.json | 0 src/{components => }/data/bookmarks.json | 0 src/{components => }/data/imprint.json | 0 src/{components => }/data/search.json | 0 src/{components => }/data/themes.json | 0 src/lib/fetcher.tsx | 166 +++++++++++++++ .../themeManager.tsx => lib/theme.tsx} | 10 +- 18 files changed, 314 insertions(+), 310 deletions(-) delete mode 100644 src/components/fetch.tsx rename src/{components => }/data/apps.json (100%) rename src/{components => }/data/bookmarks.json (100%) rename src/{components => }/data/imprint.json (100%) rename src/{components => }/data/search.json (100%) rename src/{components => }/data/themes.json (100%) create mode 100644 src/lib/fetcher.tsx rename src/{components/themeManager.tsx => lib/theme.tsx} (81%) diff --git a/src/app.tsx b/src/app.tsx index ac1799e..e0787c6 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React from "react"; import { createGlobalStyle } from "styled-components"; import SearchBar from "./components/searchBar"; @@ -8,14 +8,8 @@ import BookmarkList from "./components/bookmarkList"; import Settings from "./components/settings"; import Imprint from "./components/imprint"; -import selectedTheme from "./components/themeManager"; -import { - useAppData, - useSearchProviderData, - useBookmarkData, - useThemeData, - useImprintData, -} from "./components/fetch"; +import selectedTheme from "./lib/theme"; +import useFetcher from "./lib/fetcher"; const GlobalStyle = createGlobalStyle` body { @@ -32,12 +26,13 @@ const GlobalStyle = createGlobalStyle` } `; +/** + * Renders the entire app by calling individual components + * @returns + */ const App = () => { - const { appData } = useAppData(); - const { searchProviderData } = useSearchProviderData(); - const { bookmarkData } = useBookmarkData(); - const { themeData } = useThemeData(); - const { imprintData } = useImprintData(); + + const { appData, bookmarkData, searchProviderData, themeData, imprintData } = useFetcher(); return ( <> diff --git a/src/components/app.tsx b/src/components/app.tsx index ebcc082..fb61e13 100644 --- a/src/components/app.tsx +++ b/src/components/app.tsx @@ -1,7 +1,7 @@ import React from "react"; import Icon from "./icon"; import styled from "styled-components"; -import selectedTheme from "./themeManager"; +import selectedTheme from "../lib/theme"; const AppContainer = styled.div` display: flex; @@ -50,6 +50,11 @@ export interface IAppProps { displayURL: string; } +/** + * Renders one app in the list + * @param + * @returns + */ export const App = ({ name, icon, URL, displayURL }: IAppProps) => ( diff --git a/src/components/bookmarkGroup.tsx b/src/components/bookmarkGroup.tsx index d32305a..70a69c8 100644 --- a/src/components/bookmarkGroup.tsx +++ b/src/components/bookmarkGroup.tsx @@ -1,7 +1,7 @@ import React from "react"; import styled from "styled-components"; import { Item, SubHeadline } from "./elements"; -import selectedTheme from "./themeManager"; +import selectedTheme from "../lib/theme"; const GroupContainer = styled.div` display: flex; diff --git a/src/components/elements.tsx b/src/components/elements.tsx index 4a01e32..8cce902 100644 --- a/src/components/elements.tsx +++ b/src/components/elements.tsx @@ -1,6 +1,6 @@ import React from "react"; import styled from "styled-components"; -import selectedTheme from "./themeManager"; +import selectedTheme from "../lib/theme"; import Icon from "./icon"; // File for elements that are/can be reused across the entire site. @@ -62,6 +62,7 @@ export const Button = styled.button` const StyledButton = styled.button` float: right; border: none; + padding: 0; background: none; &:hover { @@ -69,16 +70,6 @@ const StyledButton = styled.button` } `; -export const RefreshButton = styled(Button)` - display: relative; - top: 0; - float: right; -`; - -export const ErrorMessage = styled.p` - color: red; -`; - interface IIconButtonProps { icon: string; onClick: any; diff --git a/src/components/fetch.tsx b/src/components/fetch.tsx deleted file mode 100644 index 672e985..0000000 --- a/src/components/fetch.tsx +++ /dev/null @@ -1,193 +0,0 @@ -import { useCallback, useEffect, useState } from "react"; -import { ISearchProviderProps } from "./searchBar"; -import { IBookmarkGroupProps } from "./bookmarkGroup"; -import { IAppCategoryProps } from "./appCategory"; -import { IAppProps } from "./app"; -import { IThemeProps } from "./themeManager"; -import { IImprintProps } from "./imprint"; - -const errorMessage = "Failed to load data."; - -const handleResponse = (response: any) => { - if (response.ok) return response.json(); - throw new Error(errorMessage); -}; - -// SECTION: Search Provider - -export interface ISearchProviderDataProps { - providers: Array; - error: string | boolean; -} - -export const useSearchProviderData = () => { - const [ - searchProviderData, - setSearchProviderData, - ] = useState({ providers: [], error: false }); - - const fetchSearchProviderData = useCallback(() => { - (process.env.NODE_ENV === "production" - ? fetch("/data/search.json").then(handleResponse) - : import("./data/search.json") - ) - .then((jsonResponse) => { - setSearchProviderData({ ...jsonResponse, error: false }); - }) - .catch((error) => { - setSearchProviderData({ providers: [], error: error.message }); - }); - }, []); - - useEffect(() => { - fetchSearchProviderData(); - }, [fetchSearchProviderData]); - - return { searchProviderData, fetchSearchProviderData }; -}; - -// SECTION: Bookmark data - -export interface IBookmarkDataProps { - groups: Array; - error: string | boolean; -} - -export const useBookmarkData = () => { - const [bookmarkData, setBookmarkData] = useState({ - groups: [], - error: false, - }); - - const fetchBookmarkData = useCallback(() => { - (process.env.NODE_ENV === "production" - ? fetch("/data/bookmarks.json").then(handleResponse) - : import("./data/bookmarks.json") - ) - .then((jsonResponse) => { - setBookmarkData({ ...jsonResponse, error: false }); - }) - .catch((error) => { - setBookmarkData({ groups: [], error: error.message }); - }); - }, []); - - useEffect(() => { - fetchBookmarkData(); - }, [fetchBookmarkData]); - - return { bookmarkData, fetchBookmarkData }; -}; - -// SECTION: App data - -export interface IAppDataProps { - categories: Array; - apps: Array; - error: string | boolean; -} - -export const useAppData = () => { - const [appData, setAppData] = useState({ - categories: [], - apps: [], - error: false, - }); - - const fetchAppData = useCallback(() => { - (process.env.NODE_ENV === "production" - ? fetch("/data/apps.json").then(handleResponse) - : import("./data/apps.json") - ) - .then((jsonResponse) => { - setAppData({ ...jsonResponse, error: false }); - }) - .catch((error) => { - setAppData({ categories: [], apps: [], error: error.message }); - }); - }, []); - - useEffect(() => { - fetchAppData(); - }, [fetchAppData]); - return { appData, fetchAppData }; -}; - -// Section: Theme Data - -export interface IThemeDataProps { - themes: Array; - error: string | boolean; -} - -export const useThemeData = () => { - const [themeData, setThemeData] = useState({ - themes: [], - error: false, - }); - - const fetchThemeData = useCallback(() => { - (process.env.NODE_ENV === "production" - ? fetch("/data/themes.json").then(handleResponse) - : import("./data/themes.json") - ) - .then((jsonResponse) => { - setThemeData({ ...jsonResponse, error: false }); - }) - .catch((error) => { - setThemeData({ themes: [], error: error.message }); - }); - }, []); - - useEffect(() => { - fetchThemeData(); - }, [fetchThemeData]); - return { themeData, fetchThemeData }; -}; - -// SECTION: Imprint Data - -export interface IImprintDataProps { - imprint: IImprintProps; - error: string | boolean; -} - -export const useImprintData = () => { - const [imprintData, setImprintData] = useState({ - imprint: { - name: { text: "", link: "" }, - address: { text: "", link: "" }, - phone: { text: "", link: "" }, - email: { text: "", link: "" }, - url: { text: "", link: "" }, - }, - error: false, - }); - - const fetchImprintData = useCallback(() => { - (process.env.NODE_ENV === "production" - ? fetch("/data/imprint.json").then(handleResponse) - : import("./data/imprint.json") - ) - .then((jsonResponse: any) => { - setImprintData({ ...jsonResponse, error: false }); - }) - .catch((error: any) => { - setImprintData({ - imprint: { - name: { text: "", link: "" }, - address: { text: "", link: "" }, - phone: { text: "", link: "" }, - email: { text: "", link: "" }, - url: { text: "", link: "" }, - }, - error: error.message, - }); - }); - }, []); - - useEffect(() => { - fetchImprintData(); - }, [fetchImprintData]); - return { imprintData, fetchImprintData }; -}; diff --git a/src/components/greeter.tsx b/src/components/greeter.tsx index 96af2f3..49bce77 100644 --- a/src/components/greeter.tsx +++ b/src/components/greeter.tsx @@ -1,7 +1,7 @@ import React from "react"; import styled from "styled-components"; -import selectedTheme from "./themeManager"; +import selectedTheme from "../lib/theme"; const GreeterContainer = styled.div` padding: 2rem 0; diff --git a/src/components/icon.tsx b/src/components/icon.tsx index 54a718e..9a1aff3 100644 --- a/src/components/icon.tsx +++ b/src/components/icon.tsx @@ -1,11 +1,20 @@ import React from "react"; import styled from "styled-components"; -import selectedTheme from "./themeManager"; +import selectedTheme from "../lib/theme"; -export const RawIcon = styled.i` +interface IIconProps { + name: string; + size?: string; +} + +export const Icon = ({ name, size }: IIconProps) => { + + let IconContainer = styled.i` font-family: "Material Icons"; font-weight: normal; font-style: normal; + font-size: ${size ? size : "24px"}; + color: ${selectedTheme.mainColor}; display: inline-block; line-height: 1; text-transform: none; @@ -19,18 +28,7 @@ export const RawIcon = styled.i` font-feature-settings: "liga"; `; -interface IIconProps { - name: string; - size?: string; -} - -export const ComponentIcon = ({ name, size }: IIconProps) => { - let IconContainer = styled(RawIcon)` - font-size: ${size ? size : "24px"}; - color: ${selectedTheme.mainColor}; - `; - return {name}; }; -export default ComponentIcon; +export default Icon; diff --git a/src/components/imprint.tsx b/src/components/imprint.tsx index 3990e67..324696f 100644 --- a/src/components/imprint.tsx +++ b/src/components/imprint.tsx @@ -1,7 +1,7 @@ import React from "react"; import Modal from "./modal"; import styled from "styled-components"; -import selectedTheme from "./themeManager"; +import selectedTheme from "../lib/theme"; import { ListContainer, ItemList, @@ -14,11 +14,8 @@ const Headline = styled(Hl)` padding: 1rem 0; `; -const SubHeadline = styled(SHl)` +const ModalSubHeadline = styled(SHl)` display: block; -`; - -const ModalSubHeadline = styled(SubHeadline)` padding: 0.5rem 0; `; @@ -80,6 +77,7 @@ const Imprint = ({ imprint }: IImprintComponentProps) => ( { if (window.location.href.endsWith("#imprint")) { @@ -88,7 +86,6 @@ const Imprint = ({ imprint }: IImprintComponentProps) => ( } }} > - Legal Disclosure Information in accordance with section 5 TMG diff --git a/src/components/modal.tsx b/src/components/modal.tsx index b6cc0c4..214965f 100644 --- a/src/components/modal.tsx +++ b/src/components/modal.tsx @@ -1,8 +1,8 @@ import React, { useState } from "react"; import styled from "styled-components"; -import selectedTheme from "./themeManager"; +import selectedTheme from "../lib/theme"; -import { IconButton } from "./elements"; +import { Headline, IconButton } from "./elements"; const ModalContainer = styled.div` position: absolute; @@ -30,36 +30,47 @@ const Text = styled.p` } `; +const TitleContainer = styled.div` + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: space-between; +`; + interface IModalInterface { element: string; icon?: string; text?: string; condition?: boolean; + title: string; onClose?: () => void; children: React.ReactNode; } -const Modal = (props: IModalInterface) => { - const [modalHidden, setModalHidden] = useState(props.condition ?? true); +const Modal = ({ element, icon, text, condition, title, onClose, children }: IModalInterface) => { + const [modalHidden, setModalHidden] = useState(condition ?? true); const closeModal = () => { - if (props.onClose) props.onClose(); + if (onClose) onClose(); setModalHidden(!modalHidden); }; return ( <> - {props.element === "icon" && ( - closeModal()} /> + {element === "icon" && ( + closeModal()} /> )} - {props.element === "text" && ( - closeModal()}>{props.text} + {element === "text" && ( + closeModal()}>{text} )} ); diff --git a/src/components/searchBar.tsx b/src/components/searchBar.tsx index b842c73..82910d3 100644 --- a/src/components/searchBar.tsx +++ b/src/components/searchBar.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from "react"; import styled from "styled-components"; -import selectedTheme from "./themeManager"; +import selectedTheme from "../lib/theme"; import { Button } from "./elements"; diff --git a/src/components/settings.tsx b/src/components/settings.tsx index 171bdd6..1917917 100644 --- a/src/components/settings.tsx +++ b/src/components/settings.tsx @@ -1,21 +1,31 @@ import React, { useState } from "react"; import styled from "styled-components"; -import Select from "react-select"; +import Select, { Styles } from "react-select"; import { ISearchProviderProps } from "./searchBar"; -import selectedTheme, { setTheme, IThemeProps } from "./themeManager"; -import { Button, Headline as hl } from "./elements"; +import selectedTheme, { setTheme, IThemeProps } from "../lib/theme"; +import { Button, SubHeadline } from "./elements"; import Modal from "./modal"; -const Headline = styled(hl)` - padding: 0.5rem 0; -`; +/** + * Complementary code to get hover pseudo-classes working in React + * @param color the color of the element on hover + * @param backgroundColor the background color of the element on hover + * @param border the border of the element on hover + * @param borderColor the border color of the element on hover + */ +interface IHoverProps { + color?: string; + backgroundColor?: string; + border?: string; + borderColor?: string; +} -const SelectContainer = styled.div` - padding-bottom: 1rem; -`; +interface IPseudoProps extends React.CSSProperties { + "&:hover": IHoverProps +} const FormContainer = styled.div` display: grid; @@ -42,47 +52,72 @@ const HeadCell = styled.th` background: none; `; -const SelectorStyle = { - control: (provided: any) => ({ - ...provided, - fontWeight: "500", +const Section = styled.div` + padding: 1rem 0; +`; + +const SectionHeadline = styled(SubHeadline)` + width: 100%; + border-bottom: 1px solid ${selectedTheme.accentColor}; + margin-bottom: 0.5rem; +`; + +const SelectorStyle: Partial> = { + indicatorSeparator: () => ({ + display: "none", + }), + container: (base: React.CSSProperties): React.CSSProperties => ({ + ...base, + margin: "0 2px", + }), + dropdownIndicator: (base: React.CSSProperties): IPseudoProps => ({ + ...base, + color: selectedTheme.mainColor, + "&:hover": { + color: selectedTheme.mainColor + } + }), + control: (base: React.CSSProperties): IPseudoProps => ({ + ...base, + fontWeight: 500, color: selectedTheme.mainColor, textTransform: "uppercase", width: "12rem", background: "none", - borderRadius: "0px", - border: "1px solid " + selectedTheme.mainColor, - boxShadow: 0, + borderRadius: 0, + border: "1px solid", + borderColor: selectedTheme.mainColor, + boxShadow: "none", "&:hover": { - border: "1px solid " + selectedTheme.mainColor, + border: "1px solid", + borderColor: selectedTheme.mainColor }, }), - menu: (provided: any) => ({ - ...provided, + menu: (base: React.CSSProperties): React.CSSProperties => ({ + ...base, backgroundColor: selectedTheme.backgroundColor, border: "1px solid " + selectedTheme.mainColor, borderRadius: 0, - boxShadow: 0, + boxShadow: "none", + margin: "4px 0" }), - option: (provided: any) => ({ - ...provided, - fontWeight: "500", + option: (base: React.CSSProperties): IPseudoProps => ({ + ...base, + fontWeight: 500, color: selectedTheme.mainColor, textTransform: "uppercase", borderRadius: 0, - boxShadow: 0, + boxShadow: "none", backgroundColor: selectedTheme.backgroundColor, "&:hover": { backgroundColor: selectedTheme.mainColor, color: selectedTheme.backgroundColor, }, }), - singleValue: (provided: any) => { - return { - ...provided, - color: selectedTheme.mainColor, - }; - }, + singleValue: (base: React.CSSProperties): React.CSSProperties => ({ + ...base, + color: selectedTheme.mainColor, + }), }; interface ISettingsProps { @@ -95,10 +130,13 @@ const Settings = ({ themes, providers }: ISettingsProps) => { if (themes && providers) { return ( - + {themes && ( - - Theme: + + + +
+ Theme: