Added Homepage components
This commit is contained in:
42
src/App.tsx
42
src/App.tsx
@@ -1,34 +1,24 @@
|
||||
import { useState } from 'react'
|
||||
import reactLogo from './assets/react.svg'
|
||||
import viteLogo from '/vite.svg'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import './App.css'
|
||||
import Homepage from './pages/homepage/Homepage'
|
||||
import { MantineProvider } from '@mantine/core'
|
||||
|
||||
function App() {
|
||||
const [count, setCount] = useState(0)
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
gcTime: 1000 * 60 * 60 * 24, // 24 hours
|
||||
staleTime: 1000 * 60 * 1, // 1 min
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<a href="https://vitejs.dev" target="_blank">
|
||||
<img src={viteLogo} className="logo" alt="Vite logo" />
|
||||
</a>
|
||||
<a href="https://react.dev" target="_blank">
|
||||
<img src={reactLogo} className="logo react" alt="React logo" />
|
||||
</a>
|
||||
</div>
|
||||
<h1>Vite + React</h1>
|
||||
<div className="card">
|
||||
<button onClick={() => setCount((count) => count + 1)}>
|
||||
count is {count}
|
||||
</button>
|
||||
<p>
|
||||
Edit <code>src/App.tsx</code> and save to test HMR
|
||||
</p>
|
||||
</div>
|
||||
<p className="read-the-docs">
|
||||
Click on the Vite and React logos to learn more
|
||||
</p>
|
||||
</>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MantineProvider>
|
||||
<Homepage />
|
||||
</MantineProvider>
|
||||
</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
3
src/contexts/HomepageFilters.ts
Normal file
3
src/contexts/HomepageFilters.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { atom } from 'jotai';
|
||||
|
||||
export const HomepageFilters = atom([{value: 'All', label: 'All'}]);
|
||||
1
src/hooks/index.ts
Normal file
1
src/hooks/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { useQueryAPI } from './useAPI';
|
||||
37
src/hooks/useAPI.ts
Normal file
37
src/hooks/useAPI.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
export function useQueryAPI({ endpoint }: { endpoint: string }) {
|
||||
const queryClient = useQueryClient();
|
||||
const callEndpoint = async (endpoint: string) => {
|
||||
const url = `http://localhost:5000/trigger/${endpoint}`;
|
||||
const options: object = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers': '*',
|
||||
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
|
||||
'Access-Control-Allow-Credentials': true,
|
||||
'Sec-Fetch-Mode': 'no-cors',
|
||||
'Sec-Fetch-Site': 'same-site',
|
||||
},
|
||||
};
|
||||
const response = await fetch(url, options);
|
||||
if (!response.ok) {
|
||||
throw new Error(`${response.status} ${response.statusText}`);
|
||||
}
|
||||
const responseData = await response.json();
|
||||
return responseData;
|
||||
};
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['GET', endpoint],
|
||||
queryFn: () => callEndpoint(endpoint),
|
||||
retry: false,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
staleTime: 30 * (60 * 1000), // 30 minutes
|
||||
// cacheTime: 60 * (60 * 1000), // 60 minutes
|
||||
});
|
||||
}
|
||||
65
src/pages/homepage/Homepage.tsx
Normal file
65
src/pages/homepage/Homepage.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { Stack } from '@mantine/core';
|
||||
import { useEffect, useState } from 'react';
|
||||
import stackClasses from '../../styles/Stack.module.css';
|
||||
import {useQueryAPI} from '../../hooks/useAPI';
|
||||
import Search from './components/Search';
|
||||
import Header from './components/Header';
|
||||
import Applications from './components/Applications';
|
||||
import Bookmarks from './components/Bookmarks';
|
||||
import SettingsButton from './components/SettingsButton';
|
||||
import { useAtom } from 'jotai';
|
||||
import { HomepageFilters } from '../../contexts/HomepageFilters';
|
||||
|
||||
|
||||
export default function Homepage() {
|
||||
const { data, isFetching } = useQueryAPI({ endpoint: 'api/messages' });
|
||||
const [displayedGreeting, setDisplayedGreeting] = useState('');
|
||||
const [homepageFilter, setHomepageFilter] = useAtom<any>(HomepageFilters);
|
||||
|
||||
function date() {
|
||||
const currentDate = new Date();
|
||||
const dateOptions: Intl.DateTimeFormatOptions = {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
};
|
||||
const date = currentDate.toLocaleDateString('en-GB', dateOptions);
|
||||
return date;
|
||||
}
|
||||
|
||||
function greet(list: any) {
|
||||
const currentTime = new Date();
|
||||
const greet = Math.floor(currentTime.getHours() / 6);
|
||||
const easterEggChance = Math.floor(Math.random() * 100);
|
||||
let timeBased: any = [];
|
||||
let easterEggs: any = [];
|
||||
for (const l of list) {
|
||||
if (l.easteregg === 'N') {
|
||||
timeBased = timeBased.concat(l);
|
||||
} else {
|
||||
easterEggs = easterEggs.concat(l);
|
||||
}
|
||||
}
|
||||
if (easterEggChance > 10 || easterEggs.length === 0) {
|
||||
return timeBased[greet].message;
|
||||
}
|
||||
return easterEggs[Math.floor(Math.random() * easterEggs.length)].message;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setDisplayedGreeting(greet(data));
|
||||
}
|
||||
}, [isFetching]);
|
||||
|
||||
return (
|
||||
<Stack className={stackClasses.containerStack}>
|
||||
<Search />
|
||||
<Header date={date()} greet={displayedGreeting} />
|
||||
<Applications />
|
||||
<Bookmarks />
|
||||
<SettingsButton />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
52
src/pages/homepage/components/AppItem.tsx
Normal file
52
src/pages/homepage/components/AppItem.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Icon } from '@iconify/react';
|
||||
import { Box, Stack, Text } from '@mantine/core';
|
||||
import { useState } from 'react';
|
||||
import stacks from '../../../styles/Stack.module.css';
|
||||
import texts from '../../../styles/Text.module.css';
|
||||
|
||||
function AppItem({ url, icon, name }: any) {
|
||||
const [textDecoration, setTextDecoration] = useState<string>('none');
|
||||
|
||||
return (
|
||||
<Box
|
||||
component='a'
|
||||
href={`https://${url}`}
|
||||
onMouseOver={() => setTextDecoration('underline')}
|
||||
onMouseOut={() => setTextDecoration('none')}
|
||||
className={stacks.appItemBox}
|
||||
>
|
||||
<Stack
|
||||
style={{
|
||||
height: '64px',
|
||||
marginRight: '1em',
|
||||
marginLeft: '1em',
|
||||
paddingTop: '15px',
|
||||
}}
|
||||
>
|
||||
<Icon icon={`mdi:${icon}`} height={32} width={32} />
|
||||
</Stack>
|
||||
<Stack
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
gap: 0,
|
||||
}}
|
||||
>
|
||||
<Text className={texts.appName}>{name}</Text>
|
||||
<Text
|
||||
className={texts.appUrl}
|
||||
style={{
|
||||
textDecoration: textDecoration,
|
||||
}}
|
||||
>
|
||||
{url}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default AppItem;
|
||||
51
src/pages/homepage/components/Applications.tsx
Normal file
51
src/pages/homepage/components/Applications.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Stack, Text } from '@mantine/core';
|
||||
import { useEffect, useState } from 'react';
|
||||
import {useQueryAPI} from '../../../hooks/useAPI';
|
||||
import texts from '../../../styles/Text.module.css';
|
||||
import AppItem from './AppItem';
|
||||
|
||||
function Applications() {
|
||||
const { data, isFetching } = useQueryAPI({ endpoint: 'api/apps' });
|
||||
const [applications, setApplications] = useState([]);
|
||||
const contentType = localStorage.getItem('contenttype');
|
||||
useEffect(() => {
|
||||
if (!isFetching) {
|
||||
setApplications(data);
|
||||
}
|
||||
}, [isFetching]);
|
||||
|
||||
return (
|
||||
<Stack id='apps' style={{ gap: 0 }}>
|
||||
<Text className={texts.sectionTitle}>Applications</Text>
|
||||
<Stack
|
||||
style={{
|
||||
// background: 'var(--color-background)',
|
||||
gap: '.25rem',
|
||||
backgroundSize: 'cover',
|
||||
borderBottom: '0px solid',
|
||||
display: 'grid',
|
||||
gridColumnGap: '5px',
|
||||
gridRowGap: '5px',
|
||||
gridTemplateColumns: '1fr 1fr 1fr 1fr 1fr 1fr',
|
||||
gridTemplateRows: '64px',
|
||||
paddingBottom: 'var(--module-spacing)',
|
||||
}}
|
||||
>
|
||||
{applications?.map((item: any) => {
|
||||
if (item.category === contentType || contentType === 'All')
|
||||
return (
|
||||
<AppItem
|
||||
key={item.name}
|
||||
name={item.name}
|
||||
url={item.url}
|
||||
icon={item.icon}
|
||||
category={item.category}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default Applications;
|
||||
15
src/pages/homepage/components/BookmarkGroups.tsx
Normal file
15
src/pages/homepage/components/BookmarkGroups.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import BookmarkDetails from './BookmarkDetails';
|
||||
|
||||
function BookmarkGroups(props) {
|
||||
const links = props.links;
|
||||
return (
|
||||
<div id='bookmarkForm'>
|
||||
<h3>{links?.category}</h3>
|
||||
{links?.map((links) => (
|
||||
<BookmarkDetails key={links.name} name={links.name} url={links.url} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default BookmarkGroups;
|
||||
62
src/pages/homepage/components/Bookmarks.tsx
Normal file
62
src/pages/homepage/components/Bookmarks.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { Stack, Text } from '@mantine/core';
|
||||
import { useEffect, useState } from 'react';
|
||||
import {useQueryAPI} from '../../../hooks/useAPI';
|
||||
import texts from '../../../styles/Text.module.css';
|
||||
import LinkGroups from './LinkGroups';
|
||||
|
||||
function Links() {
|
||||
const { data: linkData, isFetched: linksIsFetched } = useQueryAPI({
|
||||
endpoint: 'api/links',
|
||||
});
|
||||
const { data: groupData, isFetched: groupsIsFetched } = useQueryAPI({
|
||||
endpoint: 'api/categories',
|
||||
});
|
||||
const [bookmarks, setBookmarks] = useState([]);
|
||||
const [categories, setCategories] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (linksIsFetched && groupsIsFetched) {
|
||||
setBookmarks(linkData);
|
||||
setCategories(groupData);
|
||||
}
|
||||
}, [linksIsFetched, groupsIsFetched]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{groupsIsFetched && linksIsFetched && categories?.length > 0 && (
|
||||
<Stack id='links' style={{ gap: 0 }}>
|
||||
<Text className={texts.sectionTitle}>Bookmarks</Text>
|
||||
<Stack
|
||||
style={{
|
||||
display: 'grid',
|
||||
flexWrap: 'nowrap',
|
||||
gridColumnGap: '20px',
|
||||
gridRowGap: '0px',
|
||||
gridTemplateColumns: '1fr 1fr 1fr 1fr 1fr',
|
||||
gridTemplateRows: 'auto',
|
||||
}}
|
||||
>
|
||||
{categories.map((category: any) => {
|
||||
if (
|
||||
category.column_filter === localStorage.getItem('contenttype') ||
|
||||
localStorage.getItem('contenttype') === 'All'
|
||||
) {
|
||||
return (
|
||||
<LinkGroups
|
||||
key={category.id}
|
||||
category={category.category_name}
|
||||
links={bookmarks.filter(
|
||||
(bookmark: any) => bookmark.category === category.category_index,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</Stack>
|
||||
</Stack>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Links;
|
||||
74
src/pages/homepage/components/Header.tsx
Normal file
74
src/pages/homepage/components/Header.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import { Stack, Text } from '@mantine/core';
|
||||
import { useEffect, useState } from 'react';
|
||||
import {textClasses} from '../../../styles';
|
||||
|
||||
function Header({ greet, date }: any) {
|
||||
const [time, setTime] = useState(new Date());
|
||||
|
||||
function fixDigits(k: any) {
|
||||
if (k < 10) {
|
||||
return `0${k}`;
|
||||
}
|
||||
return k;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setInterval(() => {
|
||||
const nwDate = new Date();
|
||||
setTime(nwDate);
|
||||
}, 1000);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
style={{
|
||||
borderBottom: '0px solid',
|
||||
zIndex: '1',
|
||||
clear: 'both',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: 0,
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
style={{
|
||||
display: 'grid',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Text className={textClasses.greeting} classNames={textClasses}>{greet}</Text>
|
||||
</Stack>
|
||||
<Stack
|
||||
style={{
|
||||
display: 'flex',
|
||||
float: 'right',
|
||||
justifyContent: 'right',
|
||||
width: 'auto',
|
||||
}}
|
||||
>
|
||||
<Text className={textClasses.dateTime} classNames={textClasses} style={{ width: 'max-content' }}>
|
||||
{date}
|
||||
</Text>
|
||||
<Text
|
||||
component='h2'
|
||||
className={textClasses.dateTime}
|
||||
classNames={textClasses}
|
||||
style={{
|
||||
justifyContent: 'right',
|
||||
display: 'grid',
|
||||
}}
|
||||
>
|
||||
{`${
|
||||
time.getHours() === 0
|
||||
? 12
|
||||
: time.getHours() > 12
|
||||
? time.getHours() - 12
|
||||
: time.getHours()
|
||||
}:${fixDigits(time.getMinutes())} ${time.getHours() >= 12 ? 'PM' : 'AM'}`}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default Header;
|
||||
22
src/pages/homepage/components/LinkGroups.tsx
Normal file
22
src/pages/homepage/components/LinkGroups.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Stack, Text } from '@mantine/core';
|
||||
import texts from '../../../styles/Text.module.css';
|
||||
import LinkItem from './LinkItem';
|
||||
|
||||
function LinkGroups({ links, category }: any) {
|
||||
return (
|
||||
<Stack
|
||||
style={{
|
||||
marginBottom: '0em',
|
||||
display: 'block',
|
||||
lineHeight: '1.5rem',
|
||||
}}
|
||||
>
|
||||
<Text className={texts.linkCategory}>{category}</Text>
|
||||
{links?.map((link: any) => (
|
||||
<LinkItem key={link.name} link={link} />
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default LinkGroups;
|
||||
12
src/pages/homepage/components/LinkItem.tsx
Normal file
12
src/pages/homepage/components/LinkItem.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Text } from '@mantine/core';
|
||||
import texts from '../../../styles/Text.module.css';
|
||||
|
||||
function LinkItem({ link }: any) {
|
||||
return (
|
||||
<Text className={texts.linkItem} component='a' href={link.url}>
|
||||
{link.name}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
export default LinkItem;
|
||||
34
src/pages/homepage/components/MessageInfo.jsx
Normal file
34
src/pages/homepage/components/MessageInfo.jsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import Messages from '../../../components/Messages';
|
||||
import useQueryAPI from '../../../hooks/useAPI';
|
||||
|
||||
function MessageInfo() {
|
||||
const messageData = useQueryAPI({ endpoint: 'api/messages' });
|
||||
const [greetings, setGreetings] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!messageData.isFetching) {
|
||||
setGreetings(messageData.data);
|
||||
}
|
||||
}, [messageData.isFetching]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{greetings && (
|
||||
<div id='messageForm'>
|
||||
{greetings.map((greeting, index) => (
|
||||
<Messages
|
||||
key={greeting.id}
|
||||
group={index + 1}
|
||||
index={greeting.index}
|
||||
greeting={greeting.message}
|
||||
easterEgg={greeting.easteregg}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MessageInfo;
|
||||
12
src/pages/homepage/components/Provider.jsx
Normal file
12
src/pages/homepage/components/Provider.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
function Provider(props) {
|
||||
return (
|
||||
<tr>
|
||||
<td>
|
||||
<a href={props.provider.url}>{props.provider.name}</a>
|
||||
</td>
|
||||
<td>{props.provider.prefix}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
export default Provider;
|
||||
75
src/pages/homepage/components/Search.tsx
Normal file
75
src/pages/homepage/components/Search.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Stack, TextInput } from "@mantine/core";
|
||||
import { textInputClasses } from "../../../styles";
|
||||
import { useQueryAPI } from "../../../hooks";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function Search() {
|
||||
const { data: providers } = useQueryAPI({ endpoint: 'api/providers' });
|
||||
|
||||
const [providerLabel, setProviderLabel] = useState('Google Search');
|
||||
const [providerURL, setProviderURL] = useState('https://www.google.com/search?q=');
|
||||
|
||||
const handleKeyPress = (item: any) => {
|
||||
const option = item.target.value.substr(1, item.target.value.indexOf(' ') - 1) || item.target.value.substr(1);
|
||||
const subtext = item.target.value.substr(2 + option.length);
|
||||
if (item.key === 'Enter') {
|
||||
if (item.target.value.startsWith('/')) {
|
||||
open(`${providerURL}${subtext}`, '_self');
|
||||
} else if (validURL(item.target.value)) {
|
||||
if (containsProtocol(item.target.value)) window.location = item.target.value;
|
||||
else open(`https://${item.target.value}`, '_self');
|
||||
} else {
|
||||
open(`${providerURL}${subtext}`, '_self');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validURL(str: any) {
|
||||
const pattern = new RegExp(
|
||||
'^(https?:\\/\\/)?' + // protocol
|
||||
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
|
||||
'((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
|
||||
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
|
||||
'(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
|
||||
'(\\#[-a-z\\d_]*)?$',
|
||||
'i',
|
||||
); // fragment locator
|
||||
return !!pattern.test(str);
|
||||
}
|
||||
function containsProtocol(str: any) {
|
||||
const pattern = /^(https?:\/\/){1}.*/i;
|
||||
return !!pattern.test(str);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
textAlign: 'center',
|
||||
position: 'relative',
|
||||
padding: '.3em',
|
||||
display: 'inline-block',
|
||||
gap: 0,
|
||||
}}
|
||||
>
|
||||
<TextInput
|
||||
className={textInputClasses.keyword}
|
||||
classNames={textInputClasses}
|
||||
spellCheck='true'
|
||||
label={providerLabel}
|
||||
onChange={(item: any) => {
|
||||
if(item.target.value.startsWith('/')) {
|
||||
setProviderLabel(providers?.find((p: any) => p.prefix === item.target.value.split(' ')[0]).name);
|
||||
setProviderURL(providers?.find((p: any) => p.prefix === item.target.value.split(' ')[0]).url);
|
||||
} else {
|
||||
setProviderLabel('Google Search');
|
||||
setProviderURL('https://www.google.com/search?q=');
|
||||
}
|
||||
}}
|
||||
onKeyUp={(item: any) => {
|
||||
handleKeyPress(item);
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
97
src/pages/homepage/components/SettingsButton.tsx
Normal file
97
src/pages/homepage/components/SettingsButton.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import { Button, Menu, NavLink, Select, Stack } from '@mantine/core';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FiMenu, FiMonitor } from 'react-icons/fi';
|
||||
import {useQueryAPI} from '../../../hooks/useAPI';
|
||||
import button from '../../../styles/Button.module.css';
|
||||
import menu from '../../../styles/Menu.module.css';
|
||||
import navLink from '../../../styles/NavLink.module.css';
|
||||
import selectClasses from '../../../styles/Select.module.css';
|
||||
import { useAtom } from 'jotai';
|
||||
import { HomepageFilters } from '../../../contexts/HomepageFilters';
|
||||
|
||||
function SettingsButton() {
|
||||
const { data, isFetched } = useQueryAPI({ endpoint: 'api/appCategories' });
|
||||
const [categories, setCategories] = useState([{value: 'All', label: 'All'}]);
|
||||
const [homepageFilter, setHomepageFilter] = useAtom<object>(HomepageFilters);
|
||||
const classNames = {
|
||||
root: navLink.root,
|
||||
body: navLink.body,
|
||||
section: navLink.section,
|
||||
label: navLink.label,
|
||||
description: navLink.description,
|
||||
chevron: navLink.chevron,
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if(data) {
|
||||
setCategories(data.map((category: any) => {return {value: category.category, label: category.category}}));
|
||||
}
|
||||
}, [data, isFetched]);
|
||||
|
||||
function runWakeCommand() {
|
||||
const requestOptions = {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
fetch('/trigger/api/wakeDakota', requestOptions);
|
||||
}
|
||||
return (
|
||||
<Menu
|
||||
withinPortal
|
||||
withArrow
|
||||
shadow='md'
|
||||
position='top'
|
||||
classNames={menu}
|
||||
styles={{
|
||||
dropdown: {
|
||||
backgroundColor: 'var(--color-background)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Menu.Target>
|
||||
<Button className={button.settingsButton}>
|
||||
<FiMenu />
|
||||
</Button>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown
|
||||
style={{
|
||||
position: 'absolute',
|
||||
}}
|
||||
>
|
||||
<NavLink
|
||||
label='Settings'
|
||||
component='a'
|
||||
href='/trigger/settings'
|
||||
classNames={classNames}
|
||||
/>
|
||||
<NavLink label='Quick Actions' classNames={classNames}>
|
||||
<NavLink
|
||||
label='Wake Dakota'
|
||||
leftSection={<FiMonitor />}
|
||||
onClick={() => runWakeCommand()}
|
||||
classNames={classNames}
|
||||
/>
|
||||
</NavLink>
|
||||
<Select
|
||||
label='Homepage Filter'
|
||||
data={categories ?? []}
|
||||
defaultValue={localStorage.getItem('contenttype')}
|
||||
withCheckIcon={false}
|
||||
onChange={(data: any) => {
|
||||
localStorage.setItem('contenttype', data || 'All');
|
||||
setHomepageFilter({value: data, label: data});
|
||||
}}
|
||||
className={selectClasses.filterSelect}
|
||||
classNames={selectClasses}
|
||||
styles={{
|
||||
label: {
|
||||
color: 'var(--color-text-pri)',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
export default SettingsButton;
|
||||
6
src/styles/ActionIcon.module.css
Normal file
6
src/styles/ActionIcon.module.css
Normal file
@@ -0,0 +1,6 @@
|
||||
.root {
|
||||
background-color: transparent;
|
||||
}
|
||||
.icon {
|
||||
color: var(--color-text-acc)
|
||||
}
|
||||
26
src/styles/Button.module.css
Normal file
26
src/styles/Button.module.css
Normal file
@@ -0,0 +1,26 @@
|
||||
.settingsButton {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
color: var(--color-text-pri);
|
||||
background-color: var(--color-background);
|
||||
border-radius: 100%;
|
||||
border: solid 2px var(--color-text-pri);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.themeButton{
|
||||
font-size: 1em;
|
||||
font-weight:500;
|
||||
margin: 10px;
|
||||
width:128px;
|
||||
line-height: 3rem;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
border-radius: 1em;
|
||||
height: 3.5rem;
|
||||
}
|
||||
5
src/styles/Menu.module.css
Normal file
5
src/styles/Menu.module.css
Normal file
@@ -0,0 +1,5 @@
|
||||
.dropdown {
|
||||
background-color: var(--color-background);
|
||||
border: solid 2px var(--color-border);
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
22
src/styles/NavLink.module.css
Normal file
22
src/styles/NavLink.module.css
Normal file
@@ -0,0 +1,22 @@
|
||||
.root {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
border-radius: 4px;
|
||||
color: var(--color-text-pri);
|
||||
background-color: var(--color-background);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.root:hover {
|
||||
border-radius: 4px;
|
||||
color: var(--color-background);
|
||||
background-color: var(--color-text-acc);
|
||||
}
|
||||
|
||||
.chevron {
|
||||
width: 10px !important;
|
||||
height: 10px !important;
|
||||
}
|
||||
5
src/styles/Popover.module.css
Normal file
5
src/styles/Popover.module.css
Normal file
@@ -0,0 +1,5 @@
|
||||
.dropdown {
|
||||
background-color: var(--color-background);
|
||||
border: solid 2px var(--color-border);
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
5
src/styles/Select.module.css
Normal file
5
src/styles/Select.module.css
Normal file
@@ -0,0 +1,5 @@
|
||||
.filterSelect {
|
||||
.section {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
66
src/styles/Stack.module.css
Normal file
66
src/styles/Stack.module.css
Normal file
@@ -0,0 +1,66 @@
|
||||
.containerStack {
|
||||
align-items: stretch;
|
||||
display: grid;
|
||||
grid-column-gap: 20px;
|
||||
grid-row-gap: 1vh;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 8vh auto;
|
||||
justify-items: stretch;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 2vh;
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
.appItemBox {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
height: 64px;
|
||||
margin: 0;
|
||||
backdrop-filter: blur(10px) brightness(1.5);
|
||||
border-radius: 6px;
|
||||
font-size: 1em;
|
||||
font-weight: 400;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.settingsContainer {
|
||||
align-items: stretch;
|
||||
display: grid;
|
||||
grid-column-gap: 20px;
|
||||
grid-row-gap: 1vh;
|
||||
grid-template-columns: 25% 100%;
|
||||
grid-template-rows: 8vh auto;
|
||||
justify-items: stretch;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 2vh;
|
||||
margin-bottom: 0px;
|
||||
width: 75%;
|
||||
padding-top: .3em;
|
||||
}
|
||||
|
||||
.settingsNav {
|
||||
align-items: stretch;
|
||||
display: grid !important;
|
||||
grid-column-gap: 20px;
|
||||
grid-row-gap: 1vh;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr 14fr 1fr;
|
||||
justify-items: center;
|
||||
margin-left: 0em;
|
||||
margin-right: 0em;
|
||||
height: 95vh;
|
||||
background-color: var(--color-search-background);
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
.navItem{
|
||||
gap: 0;
|
||||
line-height: 1.5rem;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
}
|
||||
98
src/styles/Text.module.css
Normal file
98
src/styles/Text.module.css
Normal file
@@ -0,0 +1,98 @@
|
||||
.greeting {
|
||||
margin: 0;
|
||||
margin-left: .5em;
|
||||
margin-right: .5em;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
font-size: 4em;
|
||||
font-weight: 400;
|
||||
width: max-content;
|
||||
color: var(--color-text-acc);
|
||||
}
|
||||
|
||||
.dateTime {
|
||||
display: flex;
|
||||
font-size: 2em;
|
||||
height: 30px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin-right: 1em;
|
||||
text-transform: uppercase;
|
||||
font-weight: 300;
|
||||
color: var(--color-text-pri);
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
text-decoration: underline;
|
||||
font-size: 20px;
|
||||
font-weight: 200;
|
||||
font-style: italic;
|
||||
height: 10px;
|
||||
text-transform: uppercase;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
color: var(--color-text-pri);
|
||||
}
|
||||
|
||||
.appName {
|
||||
font-weight: 400;
|
||||
font-size: 0.8em;
|
||||
font-style: normal;
|
||||
color: light-dark(blue, red);
|
||||
font-family: 'Jost', sans-serif;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.appUrl {
|
||||
font-size: 0.8em;
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-text-acc);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.linkCategory {
|
||||
font-size: 1.1em;
|
||||
font-weight: 400;
|
||||
height: 10px;
|
||||
color: var(--color-text-acc);
|
||||
text-transform: uppercase;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.linkItem {
|
||||
display: block;
|
||||
font-size: 1em;
|
||||
line-height: 2;
|
||||
color: var(--color-text-pri)
|
||||
}
|
||||
|
||||
.settingsLink {
|
||||
font-size: 1.25em;
|
||||
display: block;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
line-height: 2;
|
||||
justify-self: center;
|
||||
color: var(--color-text-pri);
|
||||
border-radius: 1em;
|
||||
&:hover {
|
||||
background-color: var(--color-text-acc);
|
||||
}
|
||||
}
|
||||
|
||||
.linkItem:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a{
|
||||
color: var(--color-text-pri);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover #app-address{
|
||||
text-decoration: underline;
|
||||
text-decoration-color: var(--color-border);
|
||||
}
|
||||
71
src/styles/TextInput.module.css
Normal file
71
src/styles/TextInput.module.css
Normal file
@@ -0,0 +1,71 @@
|
||||
.keyword{
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
border-bottom: thin solid var(--color-border);
|
||||
color: var(--color-text-pri);
|
||||
font-family: -apple-system, Jost, BlinkMacSystemFont, Helvetica Neue, Roboto, sans-serif;
|
||||
font-style: italic;
|
||||
font-size: 0.8em;
|
||||
height: 3.5em;
|
||||
transition: all 0.4s ease;
|
||||
width: 50%;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.keyword:focus{
|
||||
background-color: var(--color-search-background);
|
||||
border-color: var(--color-border);
|
||||
outline: none;
|
||||
color: var(--color-text-acc);
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 1.2em;
|
||||
letter-spacing: .2em;
|
||||
width:100%;
|
||||
border-top-left-radius: .25em;
|
||||
border-top-right-radius: .25em;
|
||||
border-bottom-left-radius: .25em;
|
||||
border-bottom-right-radius: .25em;
|
||||
}
|
||||
.keyword::placeholder{
|
||||
text-align: left;
|
||||
}
|
||||
.label{
|
||||
color: var(--color-border);
|
||||
display: block;
|
||||
position: relative;
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
font-size: 1.2em;
|
||||
letter-spacing: .2em;
|
||||
opacity: 1;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
top: 28%;
|
||||
}
|
||||
.keyword:placeholder-shown + .label{
|
||||
visibility: hidden;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
}
|
||||
.keyword:focus:not(:placeholder-shown) + .label{
|
||||
visibility: visible;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
opacity: 55%;
|
||||
width: 100%;
|
||||
top: 28%;
|
||||
left: 0;
|
||||
text-align: left;
|
||||
}
|
||||
.keyword:not(:placeholder-shown) + .label{
|
||||
visibility: hidden;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
}
|
||||
11
src/styles/index.ts
Normal file
11
src/styles/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import actionIconClasses from './ActionIcon.module.css';
|
||||
import buttonClasses from './Button.module.css';
|
||||
import menuClasses from './Menu.module.css';
|
||||
import navLinkClasses from './NavLink.module.css';
|
||||
import popoverClasses from './Popover.module.css';
|
||||
import selectClasses from './Select.module.css';
|
||||
import stackClasses from './Stack.module.css';
|
||||
import textClasses from './Text.module.css';
|
||||
import textInputClasses from './TextInput.module.css';
|
||||
|
||||
export { actionIconClasses, buttonClasses, menuClasses, navLinkClasses, popoverClasses, selectClasses, stackClasses, textClasses, textInputClasses }
|
||||
Reference in New Issue
Block a user