This commit is contained in:
Sean Edmunds
2024-12-06 15:33:58 -06:00
parent 00c141d8d7
commit 33813a34fb
48 changed files with 2188 additions and 420 deletions

View File

@@ -7,6 +7,7 @@ import {
} from '@mantine/core';
import { useLocalStorage } from '@mantine/hooks';
import { ModalsProvider } from '@mantine/modals';
import { useQueryAPI } from 'hooks';
import { useEffect } from 'react';
import {
Route,
@@ -14,24 +15,22 @@ import {
createBrowserRouter,
createRoutesFromElements,
} from 'react-router-dom';
import ThemeProvider from './components/ThemeProvider';
import { useQueryAPI } from './hooks/useAPI';
import Homepage from './pages/homepage/Homepage';
import Settings from './pages/settings/Settings';
import { ThemeProvider } from './components';
import '@mantine/core/styles.css'; //import Mantine V7 styles needed by MRT
import '@mantine/dates/styles.css'; //if using mantine date picker features
import 'mantine-react-table/styles.css'; //import MRT styles
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { appTheme } from 'contexts';
import { useAtom } from 'jotai';
import { appTheme } from './contexts/theme';
import './index.css';
import { Homepage, Settings } from 'pages';
function App() {
const { data, isFetched } = useQueryAPI({
endpoint: 'api/themes',
});
const [themeName, setThemeName] = useLocalStorage<any>({
const [themeName, _] = useLocalStorage<any>({
key: 'theme',
defaultValue: 'blackboard',
});

View File

@@ -5,15 +5,15 @@ import {
useMantineReactTable,
} from 'mantine-react-table';
import { memo } from 'react';
import '../dataTableStyles.css';
import '../../../src/dataTableStyles.css';
import { ActionIcon, Button, Flex, Text } from '@mantine/core';
import { modals } from '@mantine/modals';
import { IconEdit, IconTrash } from '@tabler/icons-react';
import { appTheme } from 'contexts';
import { useMutationAPI } from 'hooks';
import { useAtom } from 'jotai';
import { FiPlus } from 'react-icons/fi';
import { appTheme } from '../contexts/theme';
import { useMutationAPI } from '../hooks/useAPI';
import { actionIconClasses, buttonClasses } from '../styles';
import { actionIconClasses, buttonClasses } from 'styles';
export function DataTableComponent({
tableName,
@@ -22,7 +22,6 @@ export function DataTableComponent({
isFetching,
isLoading,
endpoint,
grouping,
}: {
tableName: string;
columns: any;
@@ -30,9 +29,8 @@ export function DataTableComponent({
isFetching: boolean;
isLoading: boolean;
endpoint: string;
grouping?: string;
}) {
const [theme, setTheme] = useAtom(appTheme);
const [theme, _] = useAtom(appTheme);
const { mutateAsync: createRow } = useMutationAPI();
const { mutateAsync: editRow } = useMutationAPI();
const { mutateAsync: deleteRow } = useMutationAPI();

View File

@@ -1,7 +1,7 @@
import type { MRT_ColumnDef } from 'mantine-react-table';
import { useMemo } from 'react';
export default function ApplicationColumns() {
export function ApplicationColumns() {
const columns = useMemo<MRT_ColumnDef<any>[]>(
() => [
{
@@ -15,7 +15,7 @@ export default function ApplicationColumns() {
id: 'id',
header: 'ID',
size: 50,
Cell: ({ renderedCellValue, row }) => renderedCellValue,
Cell: ({ renderedCellValue }) => renderedCellValue,
Header: ({ column }) => column.columnDef.header,
},
{
@@ -27,7 +27,7 @@ export default function ApplicationColumns() {
},
id: 'name',
header: 'Name',
Cell: ({ renderedCellValue, row }) => renderedCellValue,
Cell: ({ renderedCellValue }) => renderedCellValue,
Header: ({ column }) => column.columnDef.header,
},
{
@@ -39,7 +39,7 @@ export default function ApplicationColumns() {
},
id: 'url',
header: 'Url',
Cell: ({ renderedCellValue, row }) => renderedCellValue,
Cell: ({ renderedCellValue }) => renderedCellValue,
Header: ({ column }) => column.columnDef.header,
},
{
@@ -51,7 +51,7 @@ export default function ApplicationColumns() {
},
id: 'icon',
header: 'Icon',
Cell: ({ renderedCellValue, row }) => renderedCellValue,
Cell: ({ renderedCellValue }) => renderedCellValue,
Header: ({ column }) => column.columnDef.header,
},
{
@@ -63,7 +63,7 @@ export default function ApplicationColumns() {
},
id: 'baseUrl',
header: 'Base Url',
Cell: ({ renderedCellValue, row }) => renderedCellValue,
Cell: ({ renderedCellValue }) => renderedCellValue,
Header: ({ column }) => column.columnDef.header,
},
{
@@ -75,7 +75,7 @@ export default function ApplicationColumns() {
},
id: 'filterName',
header: 'Filter Name',
Cell: ({ renderedCellValue, row }) => renderedCellValue,
Cell: ({ renderedCellValue }) => renderedCellValue,
Header: ({ column }) => column.columnDef.header,
},
],

View File

@@ -1,7 +1,7 @@
import type { MRT_ColumnDef } from 'mantine-react-table';
import { useMemo } from 'react';
export default function ApplicationColumns(groups: any) {
export function BookmarkColumns(groups: any) {
const columns = useMemo<MRT_ColumnDef<any>[]>(
() => [
{
@@ -15,7 +15,7 @@ export default function ApplicationColumns(groups: any) {
id: 'id',
header: 'ID',
size: 50,
Cell: ({ renderedCellValue, row }) => renderedCellValue,
Cell: ({ renderedCellValue }) => renderedCellValue,
Header: ({ column }) => column.columnDef.header,
},
{
@@ -27,7 +27,7 @@ export default function ApplicationColumns(groups: any) {
},
id: 'name',
header: 'Name',
Cell: ({ renderedCellValue, row }) => renderedCellValue,
Cell: ({ renderedCellValue }) => renderedCellValue,
Header: ({ column }) => column.columnDef.header,
},
{
@@ -39,7 +39,7 @@ export default function ApplicationColumns(groups: any) {
},
id: 'url',
header: 'Url',
Cell: ({ renderedCellValue, row }) => renderedCellValue,
Cell: ({ renderedCellValue }) => renderedCellValue,
Header: ({ column }) => column.columnDef.header,
},
{

View File

@@ -1,7 +1,7 @@
import type { MRT_ColumnDef } from 'mantine-react-table';
import { useMemo } from 'react';
export default function CategoryColumns() {
export function CategoryColumns() {
const columns = useMemo<MRT_ColumnDef<any>[]>(
() => [
{
@@ -15,7 +15,7 @@ export default function CategoryColumns() {
id: 'id',
header: 'ID',
size: 50,
Cell: ({ renderedCellValue, row }) => renderedCellValue,
Cell: ({ renderedCellValue }) => renderedCellValue,
Header: ({ column }) => column.columnDef.header,
},
{
@@ -28,7 +28,7 @@ export default function CategoryColumns() {
id: 'categoryName',
header: 'Name',
size: 50,
Cell: ({ renderedCellValue, row }) => renderedCellValue,
Cell: ({ renderedCellValue }) => renderedCellValue,
Header: ({ column }) => column.columnDef.header,
},
{
@@ -42,7 +42,7 @@ export default function CategoryColumns() {
id: 'categoryIndex',
header: 'Index',
size: 50,
Cell: ({ renderedCellValue, row }) => renderedCellValue,
Cell: ({ renderedCellValue }) => renderedCellValue,
Header: ({ column }) => column.columnDef.header,
},
{
@@ -55,7 +55,7 @@ export default function CategoryColumns() {
id: 'filterName',
header: 'Filter Name',
size: 50,
Cell: ({ renderedCellValue, row }) => renderedCellValue,
Cell: ({ renderedCellValue }) => renderedCellValue,
Header: ({ column }) => column.columnDef.header,
},
],

View File

@@ -1,7 +1,7 @@
import type { MRT_ColumnDef } from 'mantine-react-table';
import { useMemo } from 'react';
export default function MessagesColumns() {
export function MessagesColumns() {
const columns = useMemo<MRT_ColumnDef<any>[]>(
() => [
{
@@ -15,7 +15,7 @@ export default function MessagesColumns() {
id: 'id',
header: 'ID',
size: 50,
Cell: ({ renderedCellValue, row }) => renderedCellValue,
Cell: ({ renderedCellValue }) => renderedCellValue,
Header: ({ column }) => column.columnDef.header,
},
{
@@ -27,7 +27,7 @@ export default function MessagesColumns() {
},
id: 'message',
header: 'Message',
Cell: ({ renderedCellValue, row }) => renderedCellValue,
Cell: ({ renderedCellValue }) => renderedCellValue,
Header: ({ column }) => column.columnDef.header,
},
],

View File

@@ -1,7 +1,7 @@
import type { MRT_ColumnDef } from 'mantine-react-table';
import { useMemo } from 'react';
export default function PersonalizationColumns() {
export function PersonalizationColumns() {
const columns = useMemo<MRT_ColumnDef<any>[]>(
() => [
{
@@ -14,7 +14,7 @@ export default function PersonalizationColumns() {
id: 'firstName',
header: 'First Name',
size: 50,
Cell: ({ renderedCellValue, row }) => renderedCellValue,
Cell: ({ renderedCellValue }) => renderedCellValue,
Header: ({ column }) => column.columnDef.header,
},
{
@@ -26,7 +26,7 @@ export default function PersonalizationColumns() {
},
id: 'lastName',
header: 'Last Name',
Cell: ({ renderedCellValue, row }) => renderedCellValue,
Cell: ({ renderedCellValue }) => renderedCellValue,
Header: ({ column }) => column.columnDef.header,
},
{
@@ -38,7 +38,7 @@ export default function PersonalizationColumns() {
},
id: 'email',
header: 'Email',
Cell: ({ renderedCellValue, row }) => renderedCellValue,
Cell: ({ renderedCellValue }) => renderedCellValue,
Header: ({ column }) => column.columnDef.header,
},
],

View File

@@ -1,7 +1,7 @@
import type { MRT_ColumnDef } from 'mantine-react-table';
import { useMemo } from 'react';
export default function SearchProviderColumns() {
export function SearchProviderColumns() {
const columns = useMemo<MRT_ColumnDef<any>[]>(
() => [
{
@@ -15,7 +15,7 @@ export default function SearchProviderColumns() {
id: 'id',
header: 'ID',
size: 50,
Cell: ({ renderedCellValue, row }) => renderedCellValue,
Cell: ({ renderedCellValue }) => renderedCellValue,
Header: ({ column }) => column.columnDef.header,
},
{
@@ -27,7 +27,7 @@ export default function SearchProviderColumns() {
},
id: 'name',
header: 'Name',
Cell: ({ renderedCellValue, row }) => renderedCellValue,
Cell: ({ renderedCellValue }) => renderedCellValue,
Header: ({ column }) => column.columnDef.header,
},
{
@@ -39,7 +39,7 @@ export default function SearchProviderColumns() {
},
id: 'url',
header: 'Url',
Cell: ({ renderedCellValue, row }) => renderedCellValue,
Cell: ({ renderedCellValue }) => renderedCellValue,
Header: ({ column }) => column.columnDef.header,
},
{
@@ -51,7 +51,7 @@ export default function SearchProviderColumns() {
},
id: 'prefix',
header: 'Prefix',
Cell: ({ renderedCellValue, row }) => renderedCellValue,
Cell: ({ renderedCellValue }) => renderedCellValue,
Header: ({ column }) => column.columnDef.header,
},
],

View File

@@ -1,9 +1,9 @@
import { Divider, Group, Modal, Stack, Text } from '@mantine/core';
import { appTheme } from 'contexts';
import { useAtom } from 'jotai';
import { appTheme } from '../../../contexts/theme';
import { modalClasses } from '../../../styles';
import { modalClasses } from 'styles';
export default function HelpModal({
export function HelpModal({
setValue,
setProviderLabel,
setProviderURL,
@@ -11,7 +11,7 @@ export default function HelpModal({
providerModalOpened,
closeProviderModal,
}: any) {
const [theme, setTheme] = useAtom(appTheme);
const [theme, _] = useAtom(appTheme);
return (
<Modal.Root
size={'60%'}

View File

@@ -1,6 +1,6 @@
import { Box } from '@mantine/core';
export default function ThemeProvider({
export function ThemeProvider({
theme,
children,
}: { theme: any; children: React.ReactNode }) {

View File

@@ -1,5 +1,5 @@
import { Stack, Text } from '@mantine/core';
import { stackClasses } from '../styles';
import { stackClasses } from 'styles';
export function ToastError({ err }: { err: any }) {
const errorResponse = JSON.parse(err.message);

View File

@@ -112,7 +112,7 @@ export default function HourlyCard({ weather }: any) {
transform: `rotate(${WindDirection[weather.windDirection]}deg)`,
}}
/>
{`${weather.probabilityOfPrecipitation.value * 10}`}%
{`${weather.probabilityOfPrecipitation.value}`}%
</Group>
</Text>
</Stack>

View File

@@ -1,14 +1,14 @@
import { Center, Divider, Group, Modal, Stack } from '@mantine/core';
import { useAtom } from 'jotai';
import { modalClasses } from '../../../styles';
import { modalClasses } from 'styles';
import SmallOutlookCard from './SmallOutlookCard';
import SmallWeatherCard from './SmallWeatherCard';
import { useEffect, useState } from 'react';
import { appTheme } from '../../../contexts/theme';
import { appTheme } from '../../contexts/theme';
import HourlyCard from './HourlyCard';
export default function WeatherModal({
export function WeatherModal({
current,
outlook,
hourly,
@@ -18,7 +18,7 @@ export default function WeatherModal({
setProviderLabel,
setProviderURL,
}: any) {
const [theme, setTheme] = useAtom(appTheme);
const [theme, _] = useAtom(appTheme);
const [hourlyForecast, setHourlyForecast] = useState<any>([]);
const [sunset, setSunset] = useState(100);
@@ -30,9 +30,14 @@ export default function WeatherModal({
useEffect(() => {
if (hourlyForecast) {
setSunset(
(hourlyForecast.findIndex((item: any) => item.isDaytime === false) / 12) * 100,
);
hourlyForecast[0]?.isDaytime
? setSunset(
(hourlyForecast.findIndex((item: any) => item.isDaytime === false) / 12) *
100,
)
: setSunset(
(hourlyForecast.findIndex((item: any) => item.isDaytime === true) / 12) * 100,
);
}
}, [hourlyForecast]);
return (

10
src/components/index.ts Normal file
View File

@@ -0,0 +1,10 @@
export { HelpModal } from './HelpModal/HelpModal';
export { WeatherModal } from './WeatherModal/WeatherModal';
export { DataTable } from './DataTable/DataTable';
export { ThemeProvider } from './ThemeProvider';
export { ApplicationColumns } from './DataTable/columns/ApplicationColumns';
export { BookmarkColumns } from './DataTable/columns/BookmarkColumns';
export { CategoryColumns } from './DataTable/columns/CategoryColumns';
export { MessagesColumns } from './DataTable/columns/MessagesColumns';
export { PersonalizationColumns } from './DataTable/columns/PersonalizationColumns';
export { SearchProviderColumns } from './DataTable/columns/SearchProviderColumns';

2
src/contexts/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export { HomepageFilters } from './HomepageFilters';
export { appTheme } from './theme';

47
src/hooks/helpers.ts Normal file
View File

@@ -0,0 +1,47 @@
export function date(isMobile: boolean) {
const currentDate = new Date();
const dateOptions: Intl.DateTimeFormatOptions = !isMobile
? {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
}
: {};
const date = currentDate.toLocaleDateString('en-US', dateOptions);
return date;
}
export 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 customGreetings: any = [];
for (const l of list) {
if (l.customGreeting === 0) {
timeBased = timeBased.concat(l);
} else {
customGreetings = customGreetings.concat(l);
}
}
if (easterEggChance > 10 || customGreetings.length === 0) {
return timeBased[greet].message;
}
return customGreetings[Math.floor(Math.random() * customGreetings.length)].message;
}
export 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);
}
export function containsProtocol(str: any) {
const pattern = /^(https?:\/\/).*/i;
return !!pattern.test(str);
}

View File

@@ -1 +1,3 @@
export { useQueryAPI, useWeatherAPI } from './useAPI';
export { useQueryAPI, useWeatherAPI, useMutationAPI } from './useAPI';
export { date, greet } from './helpers';
export { validURL, containsProtocol } from './helpers';

View File

@@ -1,9 +1,4 @@
import {
useInfiniteQuery,
useMutation,
useQuery,
useQueryClient,
} from '@tanstack/react-query';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useCallback } from 'react';
import { toast } from 'react-toastify';
@@ -48,19 +43,7 @@ export function useWeatherAPI({ endpoint }: { endpoint: string }) {
} else if (endpoint === 'hourly') {
url = 'https://api.weather.gov/gridpoints/HGX/55,96/forecast/hourly';
}
const callEndpoint = async (endpoint: string) => {
const options: object = {
method: 'GET',
headers: {
'Content-Type': 'application/geo+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 callEndpoint = async () => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`${response.status} ${response.statusText}`);
@@ -70,7 +53,7 @@ export function useWeatherAPI({ endpoint }: { endpoint: string }) {
return useQuery({
queryKey: ['GET', url],
queryFn: () => callEndpoint(endpoint),
queryFn: () => callEndpoint(),
});
}
@@ -343,89 +326,8 @@ interface DownloadFileOptions {
}
export function useDownloadFile() {
const downloadFile = useCallback(
async ({ endpoint, file_name }: DownloadFileOptions) => {
const options: any = {
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 toastID = toast.loading('Processing...');
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const blob = await response.blob();
if (blob.type === 'application/json') {
const blob_text = await blob.text();
const cleaned_text = blob_text.replace(/"/g, '');
toast.update(toastID, {
render: cleaned_text,
type: 'info',
isLoading: false,
autoClose: 5000,
closeButton: null,
});
return;
}
if (typeof window !== 'undefined') {
const blobURL = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = blobURL;
a.download = file_name;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(blobURL);
document.body.removeChild(a);
toast.update(toastID, {
render: `${file_name} Downloaded`,
type: 'success',
isLoading: false,
autoClose: 5000,
closeButton: null,
});
}
} catch (error) {
console.error('Download failed:', error);
toast.update(toastID, {
render: `Error Downloading ${file_name}. Please try again.`,
type: 'error',
isLoading: false,
autoClose: 5000,
closeButton: null,
});
}
},
[],
);
return downloadFile;
}
export const useFetchInfiniteQuery = ({
endpoint,
pageSize = 10,
}: {
endpoint: string;
pageSize?: number;
}) => {
const queryKey = ['GET', endpoint];
const fetchInfiniteQuery = async (endpoint: string) => {
const requestOptions: any = {
const downloadFile = useCallback(async ({ file_name }: DownloadFileOptions) => {
const options: any = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
@@ -437,35 +339,58 @@ export const useFetchInfiniteQuery = ({
'Sec-Fetch-Site': 'same-site',
},
};
const toastID = toast.loading('Processing...');
try {
const response = await fetch(url, requestOptions);
return await response.json();
const response = await fetch(url, options);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const blob = await response.blob();
if (blob.type === 'application/json') {
const blob_text = await blob.text();
const cleaned_text = blob_text.replace(/"/g, '');
toast.update(toastID, {
render: cleaned_text,
type: 'info',
isLoading: false,
autoClose: 5000,
closeButton: null,
});
return;
}
if (typeof window !== 'undefined') {
const blobURL = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = blobURL;
a.download = file_name;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(blobURL);
document.body.removeChild(a);
toast.update(toastID, {
render: `${file_name} Downloaded`,
type: 'success',
isLoading: false,
autoClose: 5000,
closeButton: null,
});
}
} catch (error) {
console.error('Error fetching transactions:', error);
throw error;
console.error('Download failed:', error);
toast.update(toastID, {
render: `Error Downloading ${file_name}. Please try again.`,
type: 'error',
isLoading: false,
autoClose: 5000,
closeButton: null,
});
}
};
const queryFn = ({ pageParam = 1 }) =>
fetchInfiniteQuery(`${endpoint}&page=${pageParam}&size=${pageSize}`);
const getNextPageParam = (lastPage: any, allPages: any) => {
if (lastPage.items.length < pageSize) {
return undefined;
}
return allPages.length + 1;
};
const { data, isLoading, isFetchingNextPage, fetchNextPage, isError, isFetching } =
useInfiniteQuery({
queryKey,
queryFn,
getNextPageParam,
initialPageParam: 1,
});
return {
data,
isLoading,
isFetchingNextPage,
fetchNextPage,
isError,
isFetching,
};
};
}, []);
return downloadFile;
}

View File

@@ -1,18 +1,16 @@
import { Stack } from '@mantine/core';
import { useDisclosure, useLocalStorage } from '@mantine/hooks';
import { date, greet, useQueryAPI, useWeatherAPI } from 'hooks';
import { useEffect, useState } from 'react';
import { useQueryAPI, useWeatherAPI } from '../../hooks/useAPI';
import stackClasses from '../../styles/Stack.module.css';
import Applications from './components/Applications';
import Bookmarks from './components/Bookmarks';
import Header from './components/Header';
import Search from './components/Search';
import { stackClasses } from 'styles';
import type { CurrentWeather, OutlookWeather } from 'types';
import Applications from './components/Applications/Applications';
import Bookmarks from './components/Bookmarks/Bookmarks';
import Header from './components/Header/Header';
import Search from './components/Search/Search';
import SettingsButton from './components/SettingsButton';
import type { CurrentWeather, OutlookWeather } from './types/weather';
export default function Homepage({ theme }: any) {
const { data, isFetching } = useQueryAPI({ endpoint: 'api/messages' });
const [displayedGreeting, setDisplayedGreeting] = useState('');
export function Homepage({ theme }: any) {
const [appFilter, setAppFilter] = useLocalStorage<[any]>({
key: 'appFilter',
defaultValue: ['All'],
@@ -21,8 +19,12 @@ export default function Homepage({ theme }: any) {
key: 'bookmarkFilter',
defaultValue: ['All'],
});
const { data, isFetching } = useQueryAPI({ endpoint: 'api/messages' });
const { data: basicWeather } = useWeatherAPI({ endpoint: 'baseline' });
const { data: weather } = useWeatherAPI({ endpoint: 'current' });
const { data: forecast } = useWeatherAPI({ endpoint: 'forecast' });
const { data: hourly } = useWeatherAPI({ endpoint: 'hourly' });
const [displayedGreeting, setDisplayedGreeting] = useState('');
const [current, setCurrent] = useState<CurrentWeather>({
icon: '',
temperature: 0,
@@ -45,42 +47,9 @@ export default function Homepage({ theme }: any) {
windDirection: 'N',
},
]);
const { data: forecast } = useWeatherAPI({ endpoint: 'forecast' });
const { data: hourly } = useWeatherAPI({ endpoint: 'hourly' });
const isMobile = window.matchMedia('(max-width: 600px)').matches;
const [weatherModalOpened, { open: openWeatherModal, close: closeWeatherModal }] =
useDisclosure(false);
const isMobile = window.matchMedia('(max-width: 600px)').matches;
function date() {
const currentDate = new Date();
const dateOptions: Intl.DateTimeFormatOptions = !isMobile
? {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
}
: {};
const date = currentDate.toLocaleDateString('en-US', 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 customGreetings: any = [];
for (const l of list) {
if (l.customGreeting === 0) {
timeBased = timeBased.concat(l);
} else {
customGreetings = customGreetings.concat(l);
}
}
if (easterEggChance > 10 || customGreetings.length === 0) {
return timeBased[greet].message;
}
return customGreetings[Math.floor(Math.random() * customGreetings.length)].message;
}
useEffect(() => {
if (data) {
@@ -160,12 +129,12 @@ export default function Homepage({ theme }: any) {
openWeatherModal={openWeatherModal}
/>
<Header
date={date()}
date={date(isMobile)}
greet={displayedGreeting}
isMobile={isMobile}
current={current}
outlook={outlook}
setCurrent={setCurrent}
outlook={outlook}
weather={weather}
basicWeather={basicWeather}
forecast={forecast}

View File

@@ -1,8 +1,7 @@
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';
import { stackClasses, textClasses } from 'styles';
function AppItem({ url, icon, name, isMobile }: any) {
const [textDecoration, setTextDecoration] = useState<string>('none');
@@ -13,7 +12,7 @@ function AppItem({ url, icon, name, isMobile }: any) {
href={`https://${url}`}
onMouseOver={() => setTextDecoration('underline')}
onMouseOut={() => setTextDecoration('none')}
className={stacks.appItemBox}
className={stackClasses.appItemBox}
>
<Stack
align={isMobile && 'center'}
@@ -38,12 +37,12 @@ function AppItem({ url, icon, name, isMobile }: any) {
gap: 0,
}}
>
<Text className={texts.appName} classNames={texts}>
<Text className={textClasses.appName} classNames={textClasses}>
{name}
</Text>
<Text
className={texts.appUrl}
classNames={texts}
className={textClasses.appUrl}
classNames={textClasses}
style={{
textDecoration: textDecoration,
}}

View File

@@ -1,7 +1,7 @@
import { Stack, Text } from '@mantine/core';
import { useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react';
import { useQueryAPI } from '../../../hooks/useAPI';
import texts from '../../../styles/Text.module.css';
import { textClasses } from 'styles';
import AppItem from './AppItem';
function Applications({ contentType, isMobile }: any) {
@@ -16,7 +16,7 @@ function Applications({ contentType, isMobile }: any) {
<>
{applications.length > 0 && contentType.length > 0 && (
<Stack id='apps' style={{ gap: 0 }}>
<Text className={texts.sectionTitle} classNames={texts}>
<Text className={textClasses.sectionTitle} classNames={textClasses}>
Applications
</Text>
<Stack

View File

@@ -1,7 +1,7 @@
import { Stack, Text } from '@mantine/core';
import { useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react';
import { useQueryAPI } from '../../../hooks/useAPI';
import texts from '../../../styles/Text.module.css';
import { textClasses } from 'styles';
import LinkGroups from './LinkGroups';
function Links({ contentType, isMobile }: any) {
@@ -27,7 +27,7 @@ function Links({ contentType, isMobile }: any) {
categories?.length > 0 &&
contentType.length > 0 && (
<Stack id='links' style={{ gap: 0 }}>
<Text className={texts.sectionTitle} classNames={texts}>
<Text className={textClasses.sectionTitle} classNames={textClasses}>
Bookmarks
</Text>
<Stack

View File

@@ -1,5 +1,5 @@
import { Stack, Text } from '@mantine/core';
import texts from '../../../styles/Text.module.css';
import { textClasses } from 'styles';
import LinkItem from './LinkItem';
function LinkGroups({ links, category, isMobile }: any) {
@@ -12,7 +12,7 @@ function LinkGroups({ links, category, isMobile }: any) {
overflow: 'hidden',
}}
>
<Text className={texts.linkCategory} classNames={texts}>
<Text className={textClasses.linkCategory} classNames={textClasses}>
{category}
</Text>
{links?.map((link: any) => (

View File

@@ -1,11 +1,11 @@
import { Text } from '@mantine/core';
import texts from '../../../styles/Text.module.css';
import { textClasses } from 'styles';
function LinkItem({ link, isMobile }: any) {
function LinkItem({ link }: any) {
return (
<Text
className={texts.linkItem}
classNames={texts}
className={textClasses.linkItem}
classNames={textClasses}
component='a'
href={link.url}
style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}

View File

@@ -1,7 +1,7 @@
import { Group, Stack, Text } from '@mantine/core';
import { WeatherModal } from 'components';
import { useEffect, useState } from 'react';
import { textClasses } from '../../../styles';
import WeatherModal from './WeatherModal';
import { textClasses } from 'styles';
function Header({
greet,

View File

@@ -1,34 +0,0 @@
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.customGreeting}
/>
))}
</div>
)}
</>
);
}
export default MessageInfo;

View File

@@ -1,12 +0,0 @@
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;

View File

@@ -1,10 +1,10 @@
import { Stack, TextInput } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { useState } from 'react';
import { useQueryAPI } from '../../../hooks';
import { textInputClasses } from '../../../styles';
import HelpModal from './HelpModal';
import WeatherModal from './WeatherModal';
import { HelpModal } from '../../../../components/HelpModal/HelpModal';
import { WeatherModal } from '../../../../components/WeatherModal/WeatherModal';
import { containsProtocol, useQueryAPI, validURL } from '../../../../hooks';
import { textInputClasses } from '../../../../styles';
export default function Search({
current,
@@ -52,23 +52,6 @@ export default function Search({
}
};
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?:\/\/).*/i;
return !!pattern.test(str);
}
return (
<Stack
style={{

View File

@@ -1,18 +1,14 @@
import { Button, Menu, MultiSelect, NavLink } from '@mantine/core';
import { useQueryAPI } from 'hooks';
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 { buttonClasses, menuClasses, navLinkClasses, selectClasses } from 'styles';
function SettingsButton({
appFilter,
setAppFilter,
bookmarkFilter,
setBookmarkFilter,
theme,
}: any) {
const { data, isFetched } = useQueryAPI({ endpoint: 'api/appCategories' });
const [categories, setCategories] = useState([{ value: 'All', label: 'All' }]);
@@ -38,29 +34,29 @@ function SettingsButton({
return (
<Menu withinPortal={false} withArrow shadow='md' position='top'>
<Menu.Target>
<Button className={button.settingsButton} classNames={button}>
<Button className={buttonClasses.settingsButton} classNames={buttonClasses}>
<FiMenu />
</Button>
</Menu.Target>
<Menu.Dropdown className={menu.dropdown}>
<Menu.Dropdown className={menuClasses.dropdown}>
<NavLink
label='Settings'
component='a'
href={`/${import.meta.env.VITE_BASE_URL}/settings`}
className={navLink.defaultNavLink}
classNames={navLink}
className={navLinkClasses.defaultNavLink}
classNames={navLinkClasses}
/>
<NavLink
label='Quick Actions'
className={navLink.defaultNavLink}
classNames={navLink}
className={navLinkClasses.defaultNavLink}
classNames={navLinkClasses}
>
<NavLink
label='Wake Dakota'
leftSection={<FiMonitor />}
onClick={() => runWakeCommand()}
className={navLink.defaultNavLink}
classNames={navLink}
className={navLinkClasses.defaultNavLink}
classNames={navLinkClasses}
/>
</NavLink>
<MultiSelect

2
src/pages/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export { Homepage } from './homepage/Homepage';
export { Settings } from './settings/Settings';

View File

@@ -1,7 +1,6 @@
import { ActionIcon, Group, Stack, Tabs, Text } from '@mantine/core';
import { FiArrowLeftCircle } from 'react-icons/fi';
import { actionIconClasses, textClasses } from '../../styles';
import tabClasses from '../../styles/Tabs.module.css';
import { actionIconClasses, tabClasses, textClasses } from 'styles';
import Applications from './components/Applications';
import Bookmarks from './components/Bookmarks';
import Categories from './components/Categories';
@@ -10,7 +9,7 @@ import Personalization from './components/Personalization';
import SearchProviders from './components/SearchProviders';
import Themes from './components/Themes';
function Settings({ theme }: any) {
export function Settings({ theme }: any) {
return (
<Stack h={'100vh'} px={'2rem'}>
<Group gap={0}>

View File

@@ -1,9 +1,8 @@
import { Stack, Text } from '@mantine/core';
import { ApplicationColumns, DataTable } from 'components';
import { useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react';
import { DataTable } from '../../../DataTable/DataTable';
import ApplicationColumns from '../../../DataTable/columns/ApplicationColumns';
import { useQueryAPI } from '../../../hooks/useAPI';
import { textClasses } from '../../../styles';
import { textClasses } from 'styles';
const ApplicationSettings = () => {
const endpoint = 'api/apps';

View File

@@ -1,23 +1,16 @@
import { Stack, Text } from '@mantine/core';
import { BookmarkColumns, DataTable } from 'components';
import { useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react';
import { DataTable } from '../../../DataTable/DataTable';
import BookmarkColumns from '../../../DataTable/columns/BookmarkColumns';
import { useQueryAPI } from '../../../hooks/useAPI';
import { textClasses } from '../../../styles';
import { textClasses } from 'styles';
const BookmarkSettings = () => {
const endpoint = 'api/links';
const { data, isFetching, isLoading, isFetched } = useQueryAPI({
endpoint: endpoint,
});
const {
data: groupData,
isFetching: groupDataIsFetching,
isLoading: groupDataIsLoading,
isFetched: groupDataIsFetched,
} = useQueryAPI({ endpoint: 'api/categories' });
const { data: groupData } = useQueryAPI({ endpoint: 'api/categories' });
const [bookmarkData, setBookmarkData] = useState<any>([]);
const [groups, setGroups] = useState([]);
const [fullyFetched, setFullyFetched] = useState(false);
useEffect(() => {
if (isFetched) {
@@ -31,7 +24,6 @@ const BookmarkSettings = () => {
return category.categoryName;
});
setGroups(groupList);
setFullyFetched(true);
}
}, [groupData]);
return (
@@ -46,7 +38,6 @@ const BookmarkSettings = () => {
isFetching={isFetching}
isLoading={isLoading}
endpoint={endpoint}
grouping='category'
/>
</Stack>
);

View File

@@ -1,7 +1,6 @@
import { Stack, Text } from '@mantine/core';
import { CategoryColumns, DataTable } from 'components';
import { useEffect, useState } from 'react';
import { DataTable } from '../../../DataTable/DataTable';
import CategoryColumns from '../../../DataTable/columns/CategoryColumns';
import { useQueryAPI } from '../../../hooks/useAPI';
import { textClasses } from '../../../styles';
const CategorySettings = () => {

View File

@@ -1,9 +1,8 @@
import { Stack, Text } from '@mantine/core';
import { DataTable, MessagesColumns } from 'components';
import { useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react';
import { DataTable } from '../../../DataTable/DataTable';
import MessagesColumns from '../../../DataTable/columns/MessagesColumns';
import { useQueryAPI } from '../../../hooks/useAPI';
import { textClasses } from '../../../styles';
import { textClasses } from 'styles';
const MessageSettings = () => {
const endpoint = 'api/messages';
const { data, isFetching, isLoading, isFetched } = useQueryAPI({

View File

@@ -1,9 +1,8 @@
import { Text } from '@mantine/core';
import { DataTable, PersonalizationColumns } from 'components';
import { useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react';
import { DataTable } from '../../../DataTable/DataTable';
import PersonalizationColumns from '../../../DataTable/columns/PersonalizationColumns';
import { useQueryAPI } from '../../../hooks/useAPI';
import { textClasses } from '../../../styles';
import { textClasses } from 'styles';
const PersonalizationSettings = () => {
const endpoint = 'api/personalData';
const { data, isFetching, isLoading, isFetched } = useQueryAPI({

View File

@@ -1,9 +1,9 @@
import { Stack, Text } from '@mantine/core';
import { DataTable, SearchProviderColumns } from 'components';
import { useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react';
import { DataTable } from '../../../DataTable/DataTable';
import SearchProviderColumns from '../../../DataTable/columns/SearchProviderColumns';
import { useQueryAPI } from '../../../hooks/useAPI';
import { textClasses } from '../../../styles';
import { textClasses } from 'styles';
const SearchProviderSettings = () => {
const endpoint = 'api/providers';
const { data, isFetching, isLoading, isFetched } = useQueryAPI({

View File

@@ -1,7 +1,7 @@
import { Icon } from '@iconify/react/dist/iconify.js';
import { Box, Group, Stack, Text } from '@mantine/core';
import { useState } from 'react';
import { stackClasses } from '../../../styles';
import { stackClasses } from 'styles';
export default function ThemeModalContent({ selectedTheme }: any) {
const ExampleApp = (previewTheme: any) => {

View File

@@ -12,25 +12,20 @@ import {
TextInput,
} from '@mantine/core';
import { useDisclosure, useLocalStorage } from '@mantine/hooks';
import { useMutationAPI, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react';
import { FiEdit, FiPlus, FiSave, FiTrash, FiX } from 'react-icons/fi';
import { useMutationAPI, useQueryAPI } from '../../../hooks/useAPI';
import '@mantine/core/styles/ModalBase.css';
import '@mantine/core/styles/Modal.css';
import { modals } from '@mantine/modals';
import {
actionIconClasses,
buttonClasses,
modalClasses,
textClasses,
} from '../../../styles';
import { actionIconClasses, buttonClasses, modalClasses, textClasses } from 'styles';
import ThemeModalContent from './ThemeModalContent';
function ThemesSettings({ theme }: any) {
const [opened, { open, close }] = useDisclosure(false);
const [pickerOpen, { open: openPicker, close: closePicker }] = useDisclosure(false);
const [colorId, setColorId] = useState<string>('');
const [colorValue, setColorValue] = useState('#000000');
const [value, setValue] = useLocalStorage<any>({
const [_, setValue] = useLocalStorage<any>({
key: 'theme',
defaultValue: 'blackboard',
});
@@ -39,8 +34,8 @@ function ThemesSettings({ theme }: any) {
});
const [themesList, setThemesList] = useState([]);
const [selectedTheme, setSelectedTheme] = useState<any>({});
const { mutateAsync: createTheme, isPending: isCreatingTheme } = useMutationAPI();
const { mutateAsync: deleteTheme, isPending: isDeletingTheme } = useMutationAPI();
const { mutateAsync: createTheme } = useMutationAPI();
const { mutateAsync: deleteTheme } = useMutationAPI();
useEffect(() => {
if (isFetched) {
setThemesList(data);

1
src/types/index.ts Normal file
View File

@@ -0,0 +1 @@
export { type CurrentWeather, type OutlookWeather } from './weather';