This commit addresses the two currently outstanding issues and also introduces some changes:

- The port has been changed from 3000 to 8080
- The format of the imprint.json file has changed. See the current example file.
This commit is contained in:
phntxx 2021-03-05 22:00:32 +01:00
parent 15d4a60958
commit 7de67cbbd8
57 changed files with 3168 additions and 19341 deletions

View file

@ -1,18 +1,12 @@
FROM node:current-alpine FROM node:lts AS build
WORKDIR /app WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install
COPY yarn.lock . COPY . ./
COPY package.json . RUN yarn build
RUN [ "yarn", "install" ] FROM ratisbonacoding/nginx-cloudflare-cache
COPY --from=build /app/build /app
COPY data /app/data COPY nginx.conf /etc/nginx/nginx.conf
COPY src /app/src
COPY public /app/public
RUN [ "yarn", "build" ]
EXPOSE 3000 8080
CMD [ "yarn", "serve:production" ]

View file

@ -1,4 +1,5 @@
{ {
"imprint": {
"name": { "name": {
"text": "John Doe", "text": "John Doe",
"link": "#" "link": "#"
@ -19,4 +20,5 @@
"text": "example.com", "text": "example.com",
"link": "#" "link": "#"
} }
}
} }

View file

@ -7,4 +7,4 @@ services:
volumes: volumes:
- ./data:/app/data - ./data:/app/data
ports: ports:
- 3000:3000 - 8080:8080

30
nginx.conf Normal file
View file

@ -0,0 +1,30 @@
worker_processes auto;
pid /tmp/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '[$time_iso8601] $request_method $uri - $status';
server_tokens off;
sendfile on;
keepalive_timeout 65;
access_log /var/log/nginx/access.log main;
error_log /dev/null;
server {
server_name localhost;
listen 8080;
location / {
root /app;
index index.html index.htm;
}
}
}

16756
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,23 @@
/* fallback */
@font-face {
font-family: "Material Icons";
font-style: normal;
font-weight: 400;
src: url(icons.woff2) format("woff2");
}
.material-icons {
font-family: "Material Icons";
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,272 @@
/* cyrillic-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 400;
src: url(cyrillic-ext-400.woff2) format("woff2");
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F,
U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 400;
src: url(cyrillic-400.woff2) format("woff2");
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 400;
src: url(greek-ext-400.woff2) format("woff2");
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 400;
src: url(greek-400.woff2) format("woff2");
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 400;
src: url(vietnamese-400.woff2) format("woff2");
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 400;
src: url(latin-ext-400.woff2) format("woff2");
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 400;
src: url(latin-400.woff2) format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 500;
src: url(cyrillic-ext-500.woff2) format("woff2");
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F,
U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 500;
src: url(cyrillic-500.woff2) format("woff2");
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 500;
src: url(greek-ext-500.woff2) format("woff2");
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 500;
src: url(greek-500.woff2) format("woff2");
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 500;
src: url(vietnamese-500.woff2) format("woff2");
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 500;
src: url(latin-ext-500.woff2) format("woff2");
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 500;
src: url(latin-500.woff2) format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 700;
src: url(cyrillic-ext-700.woff2) format("woff2");
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F,
U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 700;
src: url(https://fonts.gstatic.com/s/roboto/v20/KFOlCnqEu92Fr1MmWUlfABc4AMP6lbBP.woff2)
format("woff2");
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 700;
src: url(greek-ext-700.woff2) format("woff2");
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 700;
src: url(greek-700.woff2) format("woff2");
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 700;
src: url(vietnamese-700.woff2) format("woff2");
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 700;
src: url(latin-ext-700.woff2) format("woff2");
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 700;
src: url(latin-700.woff2) format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 900;
src: url(cyrillic-ext-900.woff2) format("woff2");
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F,
U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 900;
src: url(cyrillic-900.woff2) format("woff2");
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 900;
src: url(greek-ext-900.woff2) format("woff2");
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 900;
src: url(greek-900.woff2) format("woff2");
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 900;
src: url(vietnamese-900.woff2) format("woff2");
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 900;
src: url(latin-ext-900.woff2) format("woff2");
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 900;
src: url(latin-900.woff2) format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
U+FEFF, U+FFFD;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -25,18 +25,8 @@
href="%PUBLIC_URL%/icon/safari-pinned-tab.svg" href="%PUBLIC_URL%/icon/safari-pinned-tab.svg"
color="#5bbad5" color="#5bbad5"
/> />
<link <link href="%PUBLIC_URL%/fonts/roboto/roboto.css" rel="stylesheet" />
href="https://fonts.googleapis.com/css2?family=Work+Sans:wght@300;400;700&display=swap" <link href="%PUBLIC_URL%/fonts/icons/icons.css" rel="stylesheet" />
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css?family=Roboto:400,500,700,900"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet"
/>
<title>Dashboard</title> <title>Dashboard</title>
</head> </head>
<body> <body>

View file

@ -1,5 +1,5 @@
import React from "react"; import React, { useEffect } from "react";
import styled, { createGlobalStyle } from "styled-components"; import { createGlobalStyle } from "styled-components";
import SearchBar from "./components/searchBar"; import SearchBar from "./components/searchBar";
import Greeter from "./components/greeter"; import Greeter from "./components/greeter";
@ -9,6 +9,13 @@ import Settings from "./components/settings";
import Imprint from "./components/imprint"; import Imprint from "./components/imprint";
import selectedTheme from "./components/themeManager"; import selectedTheme from "./components/themeManager";
import {
useAppData,
useSearchProviderData,
useBookmarkData,
useThemeData,
useImprintData,
} from "./components/fetch";
const GlobalStyle = createGlobalStyle` const GlobalStyle = createGlobalStyle`
body { body {
@ -22,24 +29,37 @@ const GlobalStyle = createGlobalStyle`
@media (min-width: 1366px) { @media (min-width: 1366px) {
max-width: 70%; max-width: 70%;
} }
} }
`; `;
const AppContainer = styled.div``; const App = () => {
const { appData } = useAppData();
const { searchProviderData } = useSearchProviderData();
const { bookmarkData } = useBookmarkData();
const { themeData } = useThemeData();
const { imprintData } = useImprintData();
const App = () => ( return (
<> <>
<GlobalStyle /> <GlobalStyle />
<AppContainer> <div>
<SearchBar /> <SearchBar providers={searchProviderData?.providers} />
<Settings /> {!themeData.error && !searchProviderData.error && (
<Settings
themes={themeData?.themes}
providers={searchProviderData?.providers}
/>
)}
<Greeter /> <Greeter />
<AppList /> {!appData.error && (
<BookmarkList /> <AppList apps={appData.apps} categories={appData.categories} />
<Imprint /> )}
</AppContainer> {!bookmarkData.error && <BookmarkList groups={bookmarkData.groups} />}
{!imprintData.error && <Imprint imprint={imprintData.imprint} />}
</div>
</> </>
); );
};
export default App; export default App;

View file

@ -11,7 +11,7 @@ const CategoryContainer = styled.div`
width: 100%; width: 100%;
`; `;
interface IAppCategoryProps { export interface IAppCategoryProps {
name: string; name: string;
items: Array<IAppProps>; items: Array<IAppProps>;
} }

View file

@ -1,47 +1,18 @@
import React, { useCallback, useEffect, useState } from "react"; import React from "react";
import { AppCategory } from "./appCategory"; import { AppCategory, IAppCategoryProps } from "./appCategory";
import { IAppProps } from "./app";
import { import { Headline, ListContainer } from "./elements";
handleResponse,
Headline,
ListContainer,
ErrorMessage,
} from "./elements";
const useAppData = () => { export interface IAppListProps {
const [appData, setAppData] = useState({ categories: Array<IAppCategoryProps>;
categories: [], apps: Array<IAppProps>;
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 };
};
const AppList = () => {
const {
appData: { categories, apps, error },
} = useAppData();
const AppList = ({ categories, apps }: IAppListProps) => {
return ( return (
<ListContainer> <ListContainer>
<Headline>Applications</Headline> <Headline>Applications</Headline>
{error && <ErrorMessage>{error}</ErrorMessage>}
{categories && {categories &&
categories.map(({ name, items }, idx) => ( categories.map(({ name, items }, idx) => (
<AppCategory key={[name, idx].join("")} name={name} items={items} /> <AppCategory key={[name, idx].join("")} name={name} items={items} />

View file

@ -22,19 +22,19 @@ const Bookmark = styled.a`
} }
`; `;
interface IBookmarkProps { export interface IBookmarkProps {
name: string; name: string;
url: string; url: string;
} }
interface IBookmarkGroupProps { export interface IBookmarkGroupProps {
name: string; name: string;
items: [IBookmarkProps]; items: Array<IBookmarkProps>;
} }
export const BookmarkGroup = ({ export const BookmarkGroup = ({
name: groupName, name: groupName,
items items,
}: IBookmarkGroupProps) => ( }: IBookmarkGroupProps) => (
<Item> <Item>
<GroupContainer> <GroupContainer>

View file

@ -1,55 +1,20 @@
import React, { useCallback, useEffect, useState } from 'react'; import React from "react";
import { import { Headline, ListContainer, ItemList } from "./elements";
handleResponse,
Headline,
ListContainer,
ItemList,
ErrorMessage,
} from './elements';
import { BookmarkGroup } from './bookmarkGroup'; import { BookmarkGroup, IBookmarkGroupProps } from "./bookmarkGroup";
const useBookmarkData = () => { interface IBookmarkListProps {
const [bookmarkData, setBookmarkData] = useState({ groups: Array<IBookmarkGroupProps>;
groups: [], }
error: false,
});
const fetchBookmarkData = useCallback(() => { const BookmarkList = ({ groups }: IBookmarkListProps) => {
(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 };
};
const BookmarkList = () => {
const {
bookmarkData: { groups, error },
} = useBookmarkData();
return ( return (
<ListContainer> <ListContainer>
<Headline>Bookmarks</Headline> <Headline>Bookmarks</Headline>
{error && <ErrorMessage>{error}</ErrorMessage>}
<ItemList> <ItemList>
{groups.map(({ name, items }, idx) => ( {groups.map(({ name, items }, idx) => (
<BookmarkGroup <BookmarkGroup key={[name, idx].join("")} name={name} items={items} />
key={[name, idx].join('')}
name={name}
items={items}
/>
))} ))}
</ItemList> </ItemList>
</ListContainer> </ListContainer>

View file

@ -1,4 +1,5 @@
{ {
"imprint": {
"name": { "name": {
"text": "John Doe", "text": "John Doe",
"link": "#" "link": "#"
@ -19,4 +20,5 @@
"text": "example.com", "text": "example.com",
"link": "#" "link": "#"
} }
}
} }

View file

@ -5,13 +5,6 @@ import Icon from "./icon";
// File for elements that are/can be reused across the entire site. // File for elements that are/can be reused across the entire site.
export const handleResponse = (response: any) => {
if (response.ok) {
return response.json();
}
throw new Error("Failed to load data.");
};
export const ListContainer = styled.div` export const ListContainer = styled.div`
padding: 2rem 0; padding: 2rem 0;
`; `;

193
src/components/fetch.tsx Normal file
View file

@ -0,0 +1,193 @@
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<ISearchProviderProps>;
error: string | boolean;
}
export const useSearchProviderData = () => {
const [
searchProviderData,
setSearchProviderData,
] = useState<ISearchProviderDataProps>({ 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<IBookmarkGroupProps>;
error: string | boolean;
}
export const useBookmarkData = () => {
const [bookmarkData, setBookmarkData] = useState<IBookmarkDataProps>({
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<IAppCategoryProps>;
apps: Array<IAppProps>;
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<IThemeProps>;
error: string | boolean;
}
export const useThemeData = () => {
const [themeData, setThemeData] = useState<IThemeDataProps>({
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<IImprintDataProps>({
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 };
};

View file

@ -1,10 +1,8 @@
import React, { useCallback, useEffect, useState } from "react"; import React from "react";
import Modal from "./modal"; import Modal from "./modal";
import styled from "styled-components"; import styled from "styled-components";
import selectedTheme from "./themeManager"; import selectedTheme from "./themeManager";
import { import {
handleResponse,
ErrorMessage,
ListContainer, ListContainer,
ItemList, ItemList,
Headline as Hl, Headline as Hl,
@ -47,60 +45,33 @@ const ItemContainer = styled.div`
padding: 1rem 0; padding: 1rem 0;
`; `;
const useImprintData = () => { interface IImprintFieldProps {
const [imprintData, setImprintData] = useState({ text: string;
name: { text: "", link: "" }, link: string;
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({
name: { text: "", link: "" },
address: { text: "", link: "" },
phone: { text: "", link: "" },
email: { text: "", link: "" },
url: { text: "", link: "" },
error: error.message,
});
});
}, []);
useEffect(() => { export interface IImprintProps {
fetchImprintData(); name: IImprintFieldProps;
}, [fetchImprintData]); address: IImprintFieldProps;
return { imprintData, fetchImprintData }; phone: IImprintFieldProps;
}; email: IImprintFieldProps;
url: IImprintFieldProps;
}
const onClose = () => { interface IImprintFieldComponentProps {
if (window.location.href.endsWith("#imprint")) { field: IImprintFieldProps;
let location = window.location.href.replace("#imprint", ""); }
window.location.href = location;
}
};
const Imprint = () => { const ImprintField = ({ field }: IImprintFieldComponentProps) => (
const { <Link href={field.link}>{field.text}</Link>
imprintData: { name, address, phone, email, url, error }, );
} = useImprintData();
useEffect(() => { interface IImprintComponentProps {
console.error(error); imprint: IImprintProps;
}, [error]); }
return ( const Imprint = ({ imprint }: IImprintComponentProps) => (
<> <>
{!error ? (
<ListContainer> <ListContainer>
<Hl>About</Hl> <Hl>About</Hl>
<ItemList> <ItemList>
@ -110,64 +81,61 @@ const Imprint = () => {
element="text" element="text"
text="View Imprint" text="View Imprint"
condition={!window.location.href.endsWith("#imprint")} condition={!window.location.href.endsWith("#imprint")}
onClose={() => onClose()} onClose={() => {
if (window.location.href.endsWith("#imprint")) {
let location = window.location.href.replace("#imprint", "");
window.location.href = location;
}
}}
> >
<Headline>Legal Disclosure</Headline> <Headline>Legal Disclosure</Headline>
{error && <ErrorMessage>{error}</ErrorMessage>}
<ModalSubHeadline> <ModalSubHeadline>
Information in accordance with section 5 TMG Information in accordance with section 5 TMG
</ModalSubHeadline> </ModalSubHeadline>
{!error && (
<> <>
<Link href={name.link}>{name.text}</Link> {imprint.name && <ImprintField field={imprint.name} />}
<Link href={address.link}>{address.text}</Link> {imprint.address && <ImprintField field={imprint.address} />}
<Link href={phone.link}>{phone.text}</Link> {imprint.email && <ImprintField field={imprint.email} />}
<Link href={email.link}>{email.text}</Link> {imprint.phone && <ImprintField field={imprint.phone} />}
<Link href={url.link}>{url.text}</Link> {imprint.url && <ImprintField field={imprint.url} />}
</> </>
)}
<Headline>Disclaimer</Headline> <Headline>Disclaimer</Headline>
<ModalSubHeadline>Accountability for content</ModalSubHeadline> <ModalSubHeadline>Accountability for content</ModalSubHeadline>
<Text> <Text>
The contents of our pages have been created with the utmost The contents of our pages have been created with the utmost care.
care. However, we cannot guarantee the contents' accuracy, However, we cannot guarantee the contents' accuracy, completeness
completeness or topicality. According to statutory provisions, or topicality. According to statutory provisions, we are
we are furthermore responsible for our own content on these furthermore responsible for our own content on these web pages. In
web pages. In this matter, please note that we are not obliged this matter, please note that we are not obliged to monitor the
to monitor the transmitted or saved information of third transmitted or saved information of third parties, or investigate
parties, or investigate circumstances pointing to illegal circumstances pointing to illegal activity. Our obligations to
activity. Our obligations to remove or block the use of remove or block the use of information under generally applicable
information under generally applicable laws remain unaffected laws remain unaffected by this as per §§ 8 to 10 of the Telemedia
by this as per §§ 8 to 10 of the Telemedia Act (TMG). Act (TMG).
</Text> </Text>
<ModalSubHeadline>Accountability for links</ModalSubHeadline> <ModalSubHeadline>Accountability for links</ModalSubHeadline>
<Text> <Text>
Responsibility for the content of external links (to web pages Responsibility for the content of external links (to web pages of
of third parties) lies solely with the operators of the linked third parties) lies solely with the operators of the linked pages.
pages. No violations were evident to us at the time of No violations were evident to us at the time of linking. Should
linking. Should any legal infringement become known to us, we any legal infringement become known to us, we will remove the
will remove the respective link immediately. respective link immediately.
</Text> </Text>
<ModalSubHeadline>Copyright</ModalSubHeadline> <ModalSubHeadline>Copyright</ModalSubHeadline>
<Text> <Text>
Our web pages and their contents are subject to German Our web pages and their contents are subject to German copyright
copyright law. Unless expressly permitted by law, every form law. Unless expressly permitted by law, every form of utilizing,
of utilizing, reproducing or processing works subject to reproducing or processing works subject to copyright protection on
copyright protection on our web pages requires the prior our web pages requires the prior consent of the respective owner
consent of the respective owner of the rights. Individual of the rights. Individual reproductions of a work are only allowed
reproductions of a work are only allowed for private use. The for private use. The materials from these pages are copyrighted
materials from these pages are copyrighted and any and any unauthorized use may violate copyright laws.
unauthorized use may violate copyright laws.
</Text> </Text>
</Modal> </Modal>
</ItemContainer> </ItemContainer>
</ItemList> </ItemList>
</ListContainer> </ListContainer>
) : (
<></>
)}
</> </>
); );
};
export default Imprint; export default Imprint;

View file

@ -1,8 +1,6 @@
import React, { useCallback, useEffect, useState } from "react"; import React, { useState } from "react";
import styled from "styled-components"; import styled from "styled-components";
import { handleResponse, ErrorMessage } from "./elements";
import selectedTheme from "./themeManager"; import selectedTheme from "./themeManager";
const SearchInput = styled.input` const SearchInput = styled.input`
@ -15,36 +13,17 @@ const SearchInput = styled.input`
color: ${selectedTheme.mainColor}; color: ${selectedTheme.mainColor};
`; `;
const useSearchProviders = () => { export interface ISearchProviderProps {
const [searchProviders, setSearchProviders] = useState({ name: string;
providers: [{ prefix: "", url: "" }], url: string;
error: false, prefix: string;
}); }
const fetchSearchProviders = useCallback(() => { interface ISearchBarProps {
(process.env.NODE_ENV === "production" providers: Array<ISearchProviderProps> | undefined;
? fetch("/data/search.json").then(handleResponse) }
: import("./data/search.json")
)
.then((jsonResponse) => {
setSearchProviders({ ...jsonResponse, error: false });
})
.catch((error) => {
setSearchProviders({ providers: [], error: error.message });
});
}, []);
useEffect(() => {
fetchSearchProviders();
}, [fetchSearchProviders]);
return { searchProviders, fetchSearchProviders };
};
const SearchBar = () => {
const {
searchProviders: { providers, error },
} = useSearchProviders();
const SearchBar = ({ providers }: ISearchBarProps) => {
let [input, setInput] = useState(""); let [input, setInput] = useState("");
const handleSearchQuery = (e: React.FormEvent) => { const handleSearchQuery = (e: React.FormEvent) => {
@ -67,12 +46,14 @@ const SearchBar = () => {
let searchQuery = queryArray.join(" "); let searchQuery = queryArray.join(" ");
let providerFound = false; let providerFound = false;
providers.forEach((provider) => { if (providers) {
providers.forEach((provider: ISearchProviderProps) => {
if (provider.prefix === prefix) { if (provider.prefix === prefix) {
providerFound = true; providerFound = true;
window.location.href = provider.url + searchQuery; window.location.href = provider.url + searchQuery;
} }
}); });
}
if (!providerFound) if (!providerFound)
window.location.href = "https://google.com/search?q=" + query; window.location.href = "https://google.com/search?q=" + query;
@ -80,7 +61,6 @@ const SearchBar = () => {
return ( return (
<form onSubmit={(e) => handleSearchQuery(e)}> <form onSubmit={(e) => handleSearchQuery(e)}>
{error && <ErrorMessage>{error}</ErrorMessage>}
<SearchInput <SearchInput
type="text" type="text"
onChange={(e) => setInput(e.target.value)} onChange={(e) => setInput(e.target.value)}

View file

@ -1,17 +1,11 @@
import React, { useCallback, useEffect, useState } from "react"; import React, { useState } from "react";
import styled from "styled-components"; import styled from "styled-components";
import Select from "react-select"; import Select from "react-select";
import searchData from "./data/search.json"; import { ISearchProviderProps } from "./searchBar";
import selectedTheme, { setTheme, IThemeProps } from "./themeManager";
import selectedTheme, { setTheme } from "./themeManager"; import { Button, Headline as hl } from "./elements";
import {
handleResponse,
Button,
ErrorMessage,
Headline as hl,
} from "./elements";
import Modal from "./modal"; import Modal from "./modal";
@ -91,42 +85,18 @@ const SelectorStyle = {
}, },
}; };
const useThemeData = () => { interface ISettingsProps {
const [themeData, setThemeData] = useState({ themes: [], error: false }); themes: Array<IThemeProps> | undefined;
providers: Array<ISearchProviderProps> | undefined;
}
const fetchThemeData = useCallback(() => { const Settings = ({ themes, providers }: ISettingsProps) => {
(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 };
};
const Settings = () => {
const [newTheme, setNewTheme] = useState(); const [newTheme, setNewTheme] = useState();
const { if (themes && providers) {
themeData: { themes, error },
} = useThemeData();
useEffect(() => {
console.log(newTheme);
}, [newTheme]);
return ( return (
<Modal element="icon" icon="settings"> <Modal element="icon" icon="settings">
{error && <ErrorMessage>{error}</ErrorMessage>} {themes && (
<SelectContainer> <SelectContainer>
<Headline>Theme:</Headline> <Headline>Theme:</Headline>
<FormContainer> <FormContainer>
@ -145,13 +115,15 @@ const Settings = () => {
<Button onClick={() => window.location.reload()}>Refresh</Button> <Button onClick={() => window.location.reload()}>Refresh</Button>
</FormContainer> </FormContainer>
</SelectContainer> </SelectContainer>
)}
{providers && (
<Table> <Table>
<tbody> <tbody>
<TableRow> <TableRow>
<HeadCell>Search Provider</HeadCell> <HeadCell>Search Provider</HeadCell>
<HeadCell>Prefix</HeadCell> <HeadCell>Prefix</HeadCell>
</TableRow> </TableRow>
{searchData.providers.map((provider, index) => ( {providers.map((provider, index) => (
<TableRow key={provider.name + index}> <TableRow key={provider.name + index}>
<TableCell>{provider.name}</TableCell> <TableCell>{provider.name}</TableCell>
<TableCell>{provider.prefix}</TableCell> <TableCell>{provider.prefix}</TableCell>
@ -159,8 +131,12 @@ const Settings = () => {
))} ))}
</tbody> </tbody>
</Table> </Table>
)}
</Modal> </Modal>
); );
} else {
return <></>;
}
}; };
export default Settings; export default Settings;

View file

@ -1,4 +1,4 @@
interface IThemeProps { export interface IThemeProps {
label: string; label: string;
value: number; value: number;
mainColor: string; mainColor: string;

3370
yarn.lock

File diff suppressed because it is too large Load diff