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
COPY package.json yarn.lock ./
RUN yarn install
COPY yarn.lock .
COPY package.json .
COPY . ./
RUN yarn build
RUN [ "yarn", "install" ]
COPY data /app/data
COPY src /app/src
COPY public /app/public
RUN [ "yarn", "build" ]
EXPOSE 3000 8080
CMD [ "yarn", "serve:production" ]
FROM ratisbonacoding/nginx-cloudflare-cache
COPY --from=build /app/build /app
COPY nginx.conf /etc/nginx/nginx.conf

View file

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

View file

@ -7,4 +7,4 @@ services:
volumes:
- ./data:/app/data
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"
color="#5bbad5"
/>
<link
href="https://fonts.googleapis.com/css2?family=Work+Sans:wght@300;400;700&display=swap"
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"
/>
<link href="%PUBLIC_URL%/fonts/roboto/roboto.css" rel="stylesheet" />
<link href="%PUBLIC_URL%/fonts/icons/icons.css" rel="stylesheet" />
<title>Dashboard</title>
</head>
<body>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,13 +5,6 @@ import Icon from "./icon";
// 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`
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 styled from "styled-components";
import selectedTheme from "./themeManager";
import {
handleResponse,
ErrorMessage,
ListContainer,
ItemList,
Headline as Hl,
@ -47,60 +45,33 @@ const ItemContainer = styled.div`
padding: 1rem 0;
`;
const useImprintData = () => {
const [imprintData, setImprintData] = useState({
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({
name: { text: "", link: "" },
address: { text: "", link: "" },
phone: { text: "", link: "" },
email: { text: "", link: "" },
url: { text: "", link: "" },
error: error.message,
});
});
}, []);
interface IImprintFieldProps {
text: string;
link: string;
}
useEffect(() => {
fetchImprintData();
}, [fetchImprintData]);
return { imprintData, fetchImprintData };
};
export interface IImprintProps {
name: IImprintFieldProps;
address: IImprintFieldProps;
phone: IImprintFieldProps;
email: IImprintFieldProps;
url: IImprintFieldProps;
}
const onClose = () => {
if (window.location.href.endsWith("#imprint")) {
let location = window.location.href.replace("#imprint", "");
window.location.href = location;
}
};
interface IImprintFieldComponentProps {
field: IImprintFieldProps;
}
const Imprint = () => {
const {
imprintData: { name, address, phone, email, url, error },
} = useImprintData();
const ImprintField = ({ field }: IImprintFieldComponentProps) => (
<Link href={field.link}>{field.text}</Link>
);
useEffect(() => {
console.error(error);
}, [error]);
interface IImprintComponentProps {
imprint: IImprintProps;
}
return (
const Imprint = ({ imprint }: IImprintComponentProps) => (
<>
{!error ? (
<ListContainer>
<Hl>About</Hl>
<ItemList>
@ -110,64 +81,61 @@ const Imprint = () => {
element="text"
text="View 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>
{error && <ErrorMessage>{error}</ErrorMessage>}
<ModalSubHeadline>
Information in accordance with section 5 TMG
</ModalSubHeadline>
{!error && (
<>
<Link href={name.link}>{name.text}</Link>
<Link href={address.link}>{address.text}</Link>
<Link href={phone.link}>{phone.text}</Link>
<Link href={email.link}>{email.text}</Link>
<Link href={url.link}>{url.text}</Link>
{imprint.name && <ImprintField field={imprint.name} />}
{imprint.address && <ImprintField field={imprint.address} />}
{imprint.email && <ImprintField field={imprint.email} />}
{imprint.phone && <ImprintField field={imprint.phone} />}
{imprint.url && <ImprintField field={imprint.url} />}
</>
)}
<Headline>Disclaimer</Headline>
<ModalSubHeadline>Accountability for content</ModalSubHeadline>
<Text>
The contents of our pages have been created with the utmost
care. However, we cannot guarantee the contents' accuracy,
completeness or topicality. According to statutory provisions,
we are furthermore responsible for our own content on these
web pages. In this matter, please note that we are not obliged
to monitor the transmitted or saved information of third
parties, or investigate circumstances pointing to illegal
activity. Our obligations to remove or block the use of
information under generally applicable laws remain unaffected
by this as per §§ 8 to 10 of the Telemedia Act (TMG).
The contents of our pages have been created with the utmost care.
However, we cannot guarantee the contents' accuracy, completeness
or topicality. According to statutory provisions, we are
furthermore responsible for our own content on these web pages. In
this matter, please note that we are not obliged to monitor the
transmitted or saved information of third parties, or investigate
circumstances pointing to illegal activity. Our obligations to
remove or block the use of information under generally applicable
laws remain unaffected by this as per §§ 8 to 10 of the Telemedia
Act (TMG).
</Text>
<ModalSubHeadline>Accountability for links</ModalSubHeadline>
<Text>
Responsibility for the content of external links (to web pages
of third parties) lies solely with the operators of the linked
pages. No violations were evident to us at the time of
linking. Should any legal infringement become known to us, we
will remove the respective link immediately.
Responsibility for the content of external links (to web pages of
third parties) lies solely with the operators of the linked pages.
No violations were evident to us at the time of linking. Should
any legal infringement become known to us, we will remove the
respective link immediately.
</Text>
<ModalSubHeadline>Copyright</ModalSubHeadline>
<Text>
Our web pages and their contents are subject to German
copyright law. Unless expressly permitted by law, every form
of utilizing, reproducing or processing works subject to
copyright protection on our web pages requires the prior
consent of the respective owner of the rights. Individual
reproductions of a work are only allowed for private use. The
materials from these pages are copyrighted and any
unauthorized use may violate copyright laws.
Our web pages and their contents are subject to German copyright
law. Unless expressly permitted by law, every form of utilizing,
reproducing or processing works subject to copyright protection on
our web pages requires the prior consent of the respective owner
of the rights. Individual reproductions of a work are only allowed
for private use. The materials from these pages are copyrighted
and any unauthorized use may violate copyright laws.
</Text>
</Modal>
</ItemContainer>
</ItemList>
</ListContainer>
) : (
<></>
)}
</>
);
};
);
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 { handleResponse, ErrorMessage } from "./elements";
import selectedTheme from "./themeManager";
const SearchInput = styled.input`
@ -15,36 +13,17 @@ const SearchInput = styled.input`
color: ${selectedTheme.mainColor};
`;
const useSearchProviders = () => {
const [searchProviders, setSearchProviders] = useState({
providers: [{ prefix: "", url: "" }],
error: false,
});
export interface ISearchProviderProps {
name: string;
url: string;
prefix: string;
}
const fetchSearchProviders = useCallback(() => {
(process.env.NODE_ENV === "production"
? 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();
interface ISearchBarProps {
providers: Array<ISearchProviderProps> | undefined;
}
const SearchBar = ({ providers }: ISearchBarProps) => {
let [input, setInput] = useState("");
const handleSearchQuery = (e: React.FormEvent) => {
@ -67,12 +46,14 @@ const SearchBar = () => {
let searchQuery = queryArray.join(" ");
let providerFound = false;
providers.forEach((provider) => {
if (providers) {
providers.forEach((provider: ISearchProviderProps) => {
if (provider.prefix === prefix) {
providerFound = true;
window.location.href = provider.url + searchQuery;
}
});
}
if (!providerFound)
window.location.href = "https://google.com/search?q=" + query;
@ -80,7 +61,6 @@ const SearchBar = () => {
return (
<form onSubmit={(e) => handleSearchQuery(e)}>
{error && <ErrorMessage>{error}</ErrorMessage>}
<SearchInput
type="text"
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 Select from "react-select";
import searchData from "./data/search.json";
import selectedTheme, { setTheme } from "./themeManager";
import {
handleResponse,
Button,
ErrorMessage,
Headline as hl,
} from "./elements";
import { ISearchProviderProps } from "./searchBar";
import selectedTheme, { setTheme, IThemeProps } from "./themeManager";
import { Button, Headline as hl } from "./elements";
import Modal from "./modal";
@ -91,42 +85,18 @@ const SelectorStyle = {
},
};
const useThemeData = () => {
const [themeData, setThemeData] = useState({ themes: [], error: false });
interface ISettingsProps {
themes: Array<IThemeProps> | undefined;
providers: Array<ISearchProviderProps> | undefined;
}
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 };
};
const Settings = () => {
const Settings = ({ themes, providers }: ISettingsProps) => {
const [newTheme, setNewTheme] = useState();
const {
themeData: { themes, error },
} = useThemeData();
useEffect(() => {
console.log(newTheme);
}, [newTheme]);
if (themes && providers) {
return (
<Modal element="icon" icon="settings">
{error && <ErrorMessage>{error}</ErrorMessage>}
{themes && (
<SelectContainer>
<Headline>Theme:</Headline>
<FormContainer>
@ -145,13 +115,15 @@ const Settings = () => {
<Button onClick={() => window.location.reload()}>Refresh</Button>
</FormContainer>
</SelectContainer>
)}
{providers && (
<Table>
<tbody>
<TableRow>
<HeadCell>Search Provider</HeadCell>
<HeadCell>Prefix</HeadCell>
</TableRow>
{searchData.providers.map((provider, index) => (
{providers.map((provider, index) => (
<TableRow key={provider.name + index}>
<TableCell>{provider.name}</TableCell>
<TableCell>{provider.prefix}</TableCell>
@ -159,8 +131,12 @@ const Settings = () => {
))}
</tbody>
</Table>
)}
</Modal>
);
} else {
return <></>;
}
};
export default Settings;

View file

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

3370
yarn.lock

File diff suppressed because it is too large Load diff