Themes modal udpated

This commit is contained in:
Sean Edmunds
2024-08-15 17:07:48 -05:00
parent a47f201fff
commit a37ab84f65
45 changed files with 1480 additions and 1177 deletions

View File

@@ -2,17 +2,17 @@ module.exports = {
root: true, root: true,
env: { browser: true, es2020: true }, env: { browser: true, es2020: true },
extends: [ extends: [
'eslint:recommended', "eslint:recommended",
'plugin:@typescript-eslint/recommended', "plugin:@typescript-eslint/recommended",
'plugin:react-hooks/recommended', "plugin:react-hooks/recommended",
], ],
ignorePatterns: ['dist', '.eslintrc.cjs'], ignorePatterns: ["dist", ".eslintrc.cjs"],
parser: '@typescript-eslint/parser', parser: "@typescript-eslint/parser",
plugins: ['react-refresh'], plugins: ["react-refresh"],
rules: { rules: {
'react-refresh/only-export-components': [ "react-refresh/only-export-components": [
'warn', "warn",
{ allowConstantExport: true }, { allowConstantExport: true },
], ],
}, },
} };

View File

@@ -1,66 +1,76 @@
import Homepage from './pages/homepage/Homepage' import Homepage from "./pages/homepage/Homepage";
import { MantineProvider, DEFAULT_THEME, createTheme, mergeMantineTheme } from '@mantine/core' import {
MantineProvider,
DEFAULT_THEME,
createTheme,
mergeMantineTheme,
} from "@mantine/core";
import { import {
Route, Route,
createBrowserRouter, createBrowserRouter,
createRoutesFromElements, createRoutesFromElements,
RouterProvider, RouterProvider,
} from "react-router-dom"; } from "react-router-dom";
import Settings from './pages/settings/Settings'; import Settings from "./pages/settings/Settings";
import { ModalsProvider } from '@mantine/modals'; import { ModalsProvider } from "@mantine/modals";
import { useLocalStorage } from '@mantine/hooks'; import { useLocalStorage } from "@mantine/hooks";
import { useEffect, useState } from 'react'; import { useEffect, useState } from "react";
import { useQueryAPI } from './hooks/useAPI'; import { useQueryAPI } from "./hooks/useAPI";
import ThemeProvider from './components/ThemeProvider'; import ThemeProvider from "./components/ThemeProvider";
import '@mantine/core/styles.css'; //import Mantine V7 styles needed by MRT 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/dates/styles.css"; //if using mantine date picker features
import 'mantine-react-table/styles.css'; //import MRT styles import "mantine-react-table/styles.css"; //import MRT styles
function App() { function App() {
const { data, isFetched } = useQueryAPI({ const { data, isFetched } = useQueryAPI({
endpoint: "api/themes", endpoint: "api/themes",
}); });
const [themeName, setThemeName] = useLocalStorage<any>({ const [themeName, setThemeName] = useLocalStorage<any>({
key: 'theme', key: "theme",
defaultValue: 'blackboard', defaultValue: "blackboard",
}); });
const [currentTheme, setCurrentTheme] = useState<any>({}); const [currentTheme, setCurrentTheme] = useState<any>({});
const overrides = createTheme({ const overrides = createTheme({
white:currentTheme.colorbackground, white: currentTheme.colorbackground,
fontFamily: '-apple-system,Jost,BlinkMacSystemFont,Helvetica Neue,Roboto,sans-serif', fontFamily:
"-apple-system,Jost,BlinkMacSystemFont,Helvetica Neue,Roboto,sans-serif",
}); });
const mantineTheme = mergeMantineTheme(DEFAULT_THEME, overrides); const mantineTheme = mergeMantineTheme(DEFAULT_THEME, overrides);
useEffect(() => { useEffect(() => {
if(data && themeName) { if (data && themeName) {
let temp = data.find((t: any) => t.name === themeName) const temp = data.find((t: any) => t.name === themeName);
setCurrentTheme(temp); setCurrentTheme(temp);
} }
}, [isFetched, themeName]) }, [isFetched, themeName]);
const router = createBrowserRouter( const router = createBrowserRouter(
createRoutesFromElements( createRoutesFromElements(
<Route> <Route>
<Route path="/trigger" element={<Homepage theme={currentTheme} />} /> <Route path="/trigger" element={<Homepage theme={currentTheme} />} />
<Route path="/trigger/settings" element={<Settings theme={currentTheme}/>} /> <Route
path="/trigger/settings"
element={<Settings theme={currentTheme} />}
/>
</Route>, </Route>,
), ),
); );
return ( return (
<MantineProvider theme={mantineTheme}> <MantineProvider theme={mantineTheme}>
{currentTheme && (
<ThemeProvider theme={currentTheme}> <ThemeProvider theme={currentTheme}>
<ModalsProvider> <ModalsProvider>
<RouterProvider router={router} /> <RouterProvider router={router} />
</ModalsProvider> </ModalsProvider>
</ThemeProvider> </ThemeProvider>
)}
</MantineProvider> </MantineProvider>
) );
} }
export default App export default App;

View File

@@ -1,5 +1,6 @@
import { MantineReactTable, useMantineReactTable } from "mantine-react-table" import { MantineReactTable, useMantineReactTable } from "mantine-react-table";
import { memo } from "react"; import { memo } from "react";
import "../dataTableStyles.css";
export function DataTableComponent({ export function DataTableComponent({
columns, columns,
@@ -9,7 +10,6 @@ export function DataTableComponent({
onCreatingRowSave, onCreatingRowSave,
onEditingRowSave, onEditingRowSave,
}: any) { }: any) {
const table = useMantineReactTable({ const table = useMantineReactTable({
columns: columns, columns: columns,
data: tableData, data: tableData,
@@ -34,57 +34,21 @@ export function DataTableComponent({
showLoadingOverlay: isLoading, showLoadingOverlay: isLoading,
isLoading: isLoading, isLoading: isLoading,
}, },
// mantineToolbarAlertBannerBadgeProps: { mantineTableHeadProps: {
// style: { style: {
// color: "red", color: "var(--color-text-pri)",
// }, },
// }, },
// mantineTopToolbarProps: { mantineTableBodyCellProps: {
// style: { style: {
// color: "var(--color-text-pri)", color: "var(--color-text-pri)",
// background: "var(--color-background)", background: "var(--color-background)",
// border: "none", borderRight: "none",
// boxShadow: "none", borderLeft: "none",
// }, },
// }, },
// mantineTableHeadProps: {
// style: {
// boxShadow: "none",
// },
// },
// mantineTableHeadCellProps: {
// style: {
// color: "var(--color-text-pri)",
// background: "var(--color-background)",
// borderRight: "none",
// borderLeft: "none",
// },
// },
// mantineTableBodyCellProps: {
// style: {
// background: "var(--color-background)",
// borderRight: "none",
// borderLeft: "none",
// },
// },
// mantinePaperProps: {
// style: {
// border: "none",
// boxShadow: "none",
// background: "var(--color-background)",
// },
// },
// mantineTableContainerProps: {
// style: {
// border: "none",
// boxShadow: "none",
// background: "var(--color-background)",
// },
// },
}); });
return ( return <MantineReactTable table={table} />;
<MantineReactTable table={table} />
);
} }
export const DataTable = memo(DataTableComponent); export const DataTable = memo(DataTableComponent);

View File

@@ -108,4 +108,3 @@ export default function ApplicationColumns() {
return columns; return columns;
} }

View File

@@ -80,4 +80,3 @@ export default function ApplicationColumns(groups: any, groupData: any) {
return columns; return columns;
} }

View File

@@ -64,4 +64,3 @@ export default function CategoryColumns() {
return columns; return columns;
} }

View File

@@ -60,4 +60,3 @@ export default function MessagesColumns() {
return columns; return columns;
} }

View File

@@ -59,4 +59,3 @@ export default function PersonalizationColumns() {
return columns; return columns;
} }

View File

@@ -60,4 +60,3 @@ export default function SearchProviderColumns() {
return columns; return columns;
} }

View File

@@ -1,11 +1,20 @@
import { Box } from '@mantine/core'; import { Box } from "@mantine/core";
export default function ThemeProvider({ theme, children }: { theme: any, children: React.ReactNode }) { export default function ThemeProvider({
return <Box style={{ theme,
'--color-text-pri': theme.colortextpri, children,
'--color-text-acc': theme.colortextacc, }: { theme: any; children: React.ReactNode }) {
'--color-background': theme.colorbackground, return (
'--color-border': theme.colorborder, <Box
'--color-search-background': theme.colorsearchbackground, style={{
}}>{children}</Box>; "--color-text-pri": theme.colortextpri,
"--color-text-acc": theme.colortextacc,
"--color-background": theme.colorbackground,
"--color-border": theme.colorborder,
"--color-search-background": theme.colorsearchbackground,
}}
>
{children}
</Box>
);
} }

View File

@@ -1,10 +1,10 @@
import { Stack, Text } from '@mantine/core'; import { Stack, Text } from "@mantine/core";
import { stackClasses } from '../styles'; import { stackClasses } from "../styles";
export function ToastError({ err }: { err: any }) { export function ToastError({ err }: { err: any }) {
const errorResponse = JSON.parse(err.message); const errorResponse = JSON.parse(err.message);
return ( return (
<Stack className={stackClasses.VStack} style={{ alignItems: 'flex-start' }}> <Stack className={stackClasses.VStack} style={{ alignItems: "flex-start" }}>
<Text> <Text>
Status: {errorResponse.status} {errorResponse.statusText} Status: {errorResponse.status} {errorResponse.statusText}
</Text> </Text>

View File

@@ -1,3 +1,3 @@
import { atom } from 'jotai'; import { atom } from "jotai";
export const HomepageFilters = atom([{value: 'All', label: 'All'}]); export const HomepageFilters = atom([{ value: "All", label: "All" }]);

View File

@@ -1,173 +1,6 @@
.mrt-table-paper {
height: 100%;
overflow: visible;
background-color: transparent;
border: none;
border-style: none;
box-shadow: none;
&.mantine-Overlay-root {
color: var(--mantine-primary-color-7);
background-color: alpha(var(--mantine-color-appGray-9), 0.5);
}
&.mrt-toolbar-dropzone {
height: 4.5vh;
width: 35vw;
color: var(--mantine-primary-color-6);
font-size: 36px;
background-color: rgb(255, 255, 255, 1);
justify-self: center;
&.inner {
font-size: 36px;
}
}
& ::-webkit-scrollbar {
width: 8px;
height: 8px;
border-radius: 5px;
background-color: transparent;
}
& ::-webkit-scrollbar-thumb:hover {
background-color: var(--mantine-primary-color-4);
}
& ::-webkit-scrollbar-track {
background-color: transparent;
border-radius: 5px;
}
& ::-webkit-scrollbar-thumb {
border-radius: 5px;
background-color: var(--mantine-primary-color-6);
}
& ::-webkit-scrollbar-corner {
background-color: light-dark(white, var(--mantine-primary-color-8));
}
}
.mrt-table-container {
height: 100%;
background-color: light-dark(white, var(--mantine-primary-color-8));
border-top: solid;
border-top-color: light-dark(
var(--mantine-color-appGray-1),
var(--mantine-primary-color-6)
);
border-top-width: 1px;
border-left: solid;
border-left-color: light-dark(
var(--mantine-color-appGray-1),
var(--mantine-primary-color-6)
);
border-left-width: 1px;
border-right: solid;
border-right-color: light-dark(
var(--mantine-color-appGray-1),
var(--mantine-primary-color-6)
);
border-right-width: 1px;
border-bottom: none;
border-radius: 4px;
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
}
.mantine-Table-th {
color: light-dark(var(--mantine-color-appGray-7), var(--mantine-primary-color-3));
background-color: light-dark(white, var(--mantine-primary-color-8));
border-color: light-dark(
var(--mantine-color-appGray-1),
var(--mantine-primary-color-7)
);
/* & .mantine-ActionIcon-root {
margin-right: 1px;
display: none;
} */
& .mrt-table-head-cell-content {
padding-top: 12px;
padding-bottom: 12px;
align-items: center;
justify-content: center;
white-space: nowrap;
.mrt-table-head-cell-labels {
& > * > .mantine-Text-root {
font-size: 14px;
font-weight: bold;
}
}
& .mrt-table-head-cell-content-actions {
transition: 1s all ease;
& .mantine-ActionIcon-root {
color: transparent;
background-color: transparent;
display: hidden;
}
}
}
& .mrt-table-head-cell-resize-handle {
display: flex;
width: 4px;
border-radius: 4px;
border-left-color: transparent;
background-color: light-dark(
var(--mantine-primary-color-1),
var(--mantine-primary-color-6)
);
}
& .mantine-TableHeadCell-Content-Labels {
cursor: pointer;
position: absolute;
}
&:hover {
& .mrt-table-head-cell-content-actions {
& .mantine-ActionIcon-root {
color: light-dark(var(--mantine-primary-color-6), var(--mantine-primary-color-3));
background-color: light-dark(
var(--mantine-primary-color-1),
var(--mantine-primary-color-6)
);
display: grid;
position: absolute;
right: 0;
}
}
& .mrt-table-head-cell-resize-handle {
border-left-color: var(--mantine-primary-color-2);
border-width: 4px;
border-left-style: solid;
}
}
}
.mantine-Mark-root {
background-color: light-dark(
var(--mantine-primary-color-7),
var(--mantine-primary-color-7)
);
color: white;
}
.mantine-Table-tr {
height: 56px;
box-shadow: none;
& .table-icon-device-floppy {
color: light-dark(var(--mantine-primary-color-6), var(--mantine-primary-color-3));
}
}
.mantine-Table-td {
color: light-dark(var(--mantine-color-appGray-7), var(--mantine-primary-color-3));
justify-content: center;
border-color: light-dark(
var(--mantine-color-appGray-1),
var(--mantine-primary-color-7)
);
& > * > .mantine-Text-root {
font-size: 14px;
}
}
.mrt-table-head-sort-button { .mrt-table-head-sort-button {
display: none; display: none;
color: var(--color-text-pri) !important;
} }
.mrt-table-head-sort-button[data-sorted='asc'] { .mrt-table-head-sort-button[data-sorted='asc'] {

View File

@@ -1 +1 @@
export { useQueryAPI } from './useAPI'; export { useQueryAPI } from "./useAPI";

View File

@@ -1,21 +1,25 @@
import { useQuery, useQueryClient, useMutation, useInfiniteQuery } from '@tanstack/react-query'; import {
import { useCallback } from 'react'; useQuery,
import { toast } from 'react-toastify'; useQueryClient,
useMutation,
useInfiniteQuery,
} from "@tanstack/react-query";
import { useCallback } from "react";
import { toast } from "react-toastify";
export function useQueryAPI({ endpoint }: { endpoint: string }) { export function useQueryAPI({ endpoint }: { endpoint: string }) {
const callEndpoint = async (endpoint: string) => { const callEndpoint = async (endpoint: string) => {
const url = `http://localhost:5000/trigger/${endpoint}`; const url = `http://localhost:5000/trigger/${endpoint}`;
const options: object = { const options: object = {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
'Access-Control-Allow-Origin': '*', "Access-Control-Allow-Origin": "*",
'Access-Control-Allow-Headers': '*', "Access-Control-Allow-Headers": "*",
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', "Access-Control-Allow-Methods": "POST, GET, OPTIONS",
'Access-Control-Allow-Credentials': true, "Access-Control-Allow-Credentials": true,
'Sec-Fetch-Mode': 'no-cors', "Sec-Fetch-Mode": "no-cors",
'Sec-Fetch-Site': 'same-site', "Sec-Fetch-Site": "same-site",
}, },
}; };
@@ -27,30 +31,28 @@ export function useQueryAPI({ endpoint }: { endpoint: string }) {
}; };
return useQuery({ return useQuery({
queryKey: ['GET', endpoint], queryKey: ["GET", endpoint],
queryFn: () => callEndpoint(endpoint), queryFn: () => callEndpoint(endpoint),
}); });
} }
export function useMutationAPI() { export function useMutationAPI() {
const callEndpoint = async ( const callEndpoint = async (
method: string, method: string,
endpoint: string, endpoint: string,
body: object | undefined | null, body: object | undefined | null,
) => { ) => {
const url = `http://localhost:5000/trigger/${endpoint}`; const url = `http://localhost:5000/trigger/${endpoint}`;
const options: any = { const options: any = {
method: method, method: method,
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
'Access-Control-Allow-Origin': '*', "Access-Control-Allow-Origin": "*",
'Access-Control-Allow-Headers': '*', "Access-Control-Allow-Headers": "*",
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', "Access-Control-Allow-Methods": "POST, GET, OPTIONS",
'Access-Control-Allow-Credentials': true, "Access-Control-Allow-Credentials": true,
'Sec-Fetch-Mode': 'no-cors', "Sec-Fetch-Mode": "no-cors",
'Sec-Fetch-Site': 'same-site' "Sec-Fetch-Site": "same-site",
}, },
body: JSON.stringify(body), body: JSON.stringify(body),
}; };
@@ -58,7 +60,7 @@ export function useMutationAPI() {
const response = await fetch(url, options); const response = await fetch(url, options);
if (!response.ok) { if (!response.ok) {
const responseData = await response.json(); const responseData = await response.json();
if (typeof responseData.detail !== 'string') { if (typeof responseData.detail !== "string") {
const errorResponse = JSON.stringify({ const errorResponse = JSON.stringify({
status: response.status, status: response.status,
statusText: response.statusText, statusText: response.statusText,
@@ -71,7 +73,7 @@ export function useMutationAPI() {
throw new Error(errorResponse); throw new Error(errorResponse);
} }
if (typeof responseData.detail === 'object') { if (typeof responseData.detail === "object") {
const errorResponse = JSON.stringify({ const errorResponse = JSON.stringify({
status: response.status, status: response.status,
statusText: response.statusText, statusText: response.statusText,
@@ -108,16 +110,18 @@ export function useMutationAPI() {
}) => callEndpoint(method, endpoint, body), }) => callEndpoint(method, endpoint, body),
onMutate: () => {}, onMutate: () => {},
onSuccess: (data, variables) => { onSuccess: (data, variables) => {
queryClient.invalidateQueries({ queryKey: ['GET', variables.endpoint] }); queryClient.invalidateQueries({ queryKey: ["GET", variables.endpoint] });
if (variables.endpoint.includes('/')) { if (variables.endpoint.includes("/")) {
if ((variables.endpoint.match(/\//g) || []).length > 2) { if ((variables.endpoint.match(/\//g) || []).length > 2) {
const GetEndpoint = variables.endpoint.split('/'); const GetEndpoint = variables.endpoint.split("/");
const Endpoint = `${GetEndpoint[1]}/${GetEndpoint[2]}`; const Endpoint = `${GetEndpoint[1]}/${GetEndpoint[2]}`;
queryClient.invalidateQueries({ queryKey: ['GET', `/${Endpoint}`] }); queryClient.invalidateQueries({ queryKey: ["GET", `/${Endpoint}`] });
} else { } else {
const GetEndpoint = variables.endpoint.split('/')[1]; const GetEndpoint = variables.endpoint.split("/")[1];
queryClient.invalidateQueries({ queryKey: ['GET', `/${GetEndpoint}`] }); queryClient.invalidateQueries({
queryKey: ["GET", `/${GetEndpoint}`],
});
} }
} }
}, },
@@ -125,32 +129,30 @@ export function useMutationAPI() {
} }
export function useFileUploadAPI() { export function useFileUploadAPI() {
const callEndpoint = async ( const callEndpoint = async (
method: string, method: string,
endpoint: string, endpoint: string,
body?: object | FormData | undefined | null, body?: object | FormData | undefined | null,
) => { ) => {
const url = `http://localhost:5000/trigger/${endpoint}`; const url = `http://localhost:5000/trigger/${endpoint}`;
const options = { const options = {
method: method, method: method,
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
'Access-Control-Allow-Origin': '*', "Access-Control-Allow-Origin": "*",
'Access-Control-Allow-Headers': '*', "Access-Control-Allow-Headers": "*",
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', "Access-Control-Allow-Methods": "POST, GET, OPTIONS",
'Access-Control-Allow-Credentials': true, "Access-Control-Allow-Credentials": true,
'Sec-Fetch-Mode': 'no-cors', "Sec-Fetch-Mode": "no-cors",
'Sec-Fetch-Site': 'same-site', "Sec-Fetch-Site": "same-site",
}, },
body: body ?? undefined, body: body ?? undefined,
}; };
const response = await fetch(url, options as unknown as Body); const response = await fetch(url, options as unknown as Body);
if (!response.ok) { if (!response.ok) {
const responseData = await response.json(); const responseData = await response.json();
if (typeof responseData.detail !== 'string') { if (typeof responseData.detail !== "string") {
const errorResponse = JSON.stringify({ const errorResponse = JSON.stringify({
status: response.status, status: response.status,
statusText: response.statusText, statusText: response.statusText,
@@ -190,10 +192,10 @@ export function useFileUploadAPI() {
}) => callEndpoint(method, endpoint, body), }) => callEndpoint(method, endpoint, body),
onMutate: () => {}, onMutate: () => {},
onSuccess: (variables) => { onSuccess: (variables) => {
queryClient.invalidateQueries({ queryKey: ['GET', variables.endpoint] }); queryClient.invalidateQueries({ queryKey: ["GET", variables.endpoint] });
if (variables.endpoint?.includes('/')) { if (variables.endpoint?.includes("/")) {
const GetEndpoint = variables.endpoint.split('/')[1]; const GetEndpoint = variables.endpoint.split("/")[1];
queryClient.invalidateQueries({ queryKey: ['GET', `/${GetEndpoint}`] }); queryClient.invalidateQueries({ queryKey: ["GET", `/${GetEndpoint}`] });
} }
}, },
}); });
@@ -204,12 +206,11 @@ export function useFetchAllRecordsAPI({ endpoint }: { endpoint: string }) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const fetchPage = async (page: number, retryCount = 0): Promise<any> => { const fetchPage = async (page: number, retryCount = 0): Promise<any> => {
// Start building the URL // Start building the URL
const url = new URL(`http://localhost:5000/trigger/${endpoint}`); const url = new URL(`http://localhost:5000/trigger/${endpoint}`);
// Check and append custom query parameters from the endpoint // Check and append custom query parameters from the endpoint
const customParams = new URLSearchParams(endpoint.split('?')[1]); const customParams = new URLSearchParams(endpoint.split("?")[1]);
for (const [key, value] of customParams) { for (const [key, value] of customParams) {
if (!url.searchParams.has(key)) { if (!url.searchParams.has(key)) {
url.searchParams.append(key, value); url.searchParams.append(key, value);
@@ -217,19 +218,19 @@ export function useFetchAllRecordsAPI({ endpoint }: { endpoint: string }) {
} }
// Add 'page' and 'size' parameters // Add 'page' and 'size' parameters
url.searchParams.set('page', page.toString()); // Use 'set' to ensure uniqueness url.searchParams.set("page", page.toString()); // Use 'set' to ensure uniqueness
url.searchParams.set('size', pageSize.toString()); url.searchParams.set("size", pageSize.toString());
const options: any = { const options: any = {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
'Access-Control-Allow-Origin': '*', "Access-Control-Allow-Origin": "*",
'Access-Control-Allow-Headers': '*', "Access-Control-Allow-Headers": "*",
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', "Access-Control-Allow-Methods": "POST, GET, OPTIONS",
'Access-Control-Allow-Credentials': true, "Access-Control-Allow-Credentials": true,
'Sec-Fetch-Mode': 'no-cors', "Sec-Fetch-Mode": "no-cors",
'Sec-Fetch-Site': 'same-site', "Sec-Fetch-Site": "same-site",
}, },
}; };
@@ -294,18 +295,18 @@ export function useFetchAllRecordsAPI({ endpoint }: { endpoint: string }) {
return allItems; return allItems;
} catch (error) { } catch (error) {
// Handling errors in fetching the first page // Handling errors in fetching the first page
console.error('Error fetching the first page:', error); console.error("Error fetching the first page:", error);
return []; // Return empty array on error return []; // Return empty array on error
} }
}; };
const invalidate = useCallback(() => { const invalidate = useCallback(() => {
queryClient.invalidateQueries({ queryKey: ['GET', endpoint] }); queryClient.invalidateQueries({ queryKey: ["GET", endpoint] });
}, [queryClient, endpoint]); }, [queryClient, endpoint]);
return { return {
...useQuery({ ...useQuery({
queryKey: ['GET', endpoint], queryKey: ["GET", endpoint],
queryFn: callEndpoint, queryFn: callEndpoint,
}), }),
invalidate, invalidate,
@@ -322,31 +323,31 @@ export function useDownloadFile() {
async ({ endpoint, file_name }: DownloadFileOptions) => { async ({ endpoint, file_name }: DownloadFileOptions) => {
const url = `http://localhost:5000/trigger/${endpoint}`; const url = `http://localhost:5000/trigger/${endpoint}`;
const options: any = { const options: any = {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
'Access-Control-Allow-Origin': '*', "Access-Control-Allow-Origin": "*",
'Access-Control-Allow-Headers': '*', "Access-Control-Allow-Headers": "*",
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', "Access-Control-Allow-Methods": "POST, GET, OPTIONS",
'Access-Control-Allow-Credentials': true, "Access-Control-Allow-Credentials": true,
'Sec-Fetch-Mode': 'no-cors', "Sec-Fetch-Mode": "no-cors",
'Sec-Fetch-Site': 'same-site', "Sec-Fetch-Site": "same-site",
}, },
}; };
const toastID = toast.loading('Processing...'); const toastID = toast.loading("Processing...");
try { try {
const response = await fetch(url, options); const response = await fetch(url, options);
if (!response.ok) { if (!response.ok) {
throw new Error('Network response was not ok'); throw new Error("Network response was not ok");
} }
const blob = await response.blob(); const blob = await response.blob();
if (blob.type === 'application/json') { if (blob.type === "application/json") {
const blob_text = await blob.text(); const blob_text = await blob.text();
const cleaned_text = blob_text.replace(/"/g, ''); const cleaned_text = blob_text.replace(/"/g, "");
toast.update(toastID, { toast.update(toastID, {
render: cleaned_text, render: cleaned_text,
type: 'info', type: "info",
isLoading: false, isLoading: false,
autoClose: 5000, autoClose: 5000,
closeButton: null, closeButton: null,
@@ -354,11 +355,11 @@ export function useDownloadFile() {
return; return;
} }
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
const blobURL = window.URL.createObjectURL(blob); const blobURL = window.URL.createObjectURL(blob);
const a = document.createElement('a'); const a = document.createElement("a");
a.style.display = 'none'; a.style.display = "none";
a.href = blobURL; a.href = blobURL;
a.download = file_name; a.download = file_name;
@@ -368,17 +369,17 @@ export function useDownloadFile() {
document.body.removeChild(a); document.body.removeChild(a);
toast.update(toastID, { toast.update(toastID, {
render: `${file_name} Downloaded`, render: `${file_name} Downloaded`,
type: 'success', type: "success",
isLoading: false, isLoading: false,
autoClose: 5000, autoClose: 5000,
closeButton: null, closeButton: null,
}); });
} }
} catch (error) { } catch (error) {
console.error('Download failed:', error); console.error("Download failed:", error);
toast.update(toastID, { toast.update(toastID, {
render: `Error Downloading ${file_name}. Please try again.`, render: `Error Downloading ${file_name}. Please try again.`,
type: 'error', type: "error",
isLoading: false, isLoading: false,
autoClose: 5000, autoClose: 5000,
closeButton: null, closeButton: null,
@@ -391,7 +392,6 @@ export function useDownloadFile() {
return downloadFile; return downloadFile;
} }
export const useFetchInfiniteQuery = ({ export const useFetchInfiniteQuery = ({
endpoint, endpoint,
pageSize = 10, pageSize = 10,
@@ -399,27 +399,27 @@ export const useFetchInfiniteQuery = ({
endpoint: string; endpoint: string;
pageSize?: number; pageSize?: number;
}) => { }) => {
const queryKey = ['GET', endpoint]; const queryKey = ["GET", endpoint];
const fetchInfiniteQuery = async (endpoint: string) => { const fetchInfiniteQuery = async (endpoint: string) => {
const url = `http://localhost:5000/trigger/${endpoint}`; const url = `http://localhost:5000/trigger/${endpoint}`;
const requestOptions: any = { const requestOptions: any = {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
'Access-Control-Allow-Origin': '*', "Access-Control-Allow-Origin": "*",
'Access-Control-Allow-Headers': '*', "Access-Control-Allow-Headers": "*",
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', "Access-Control-Allow-Methods": "POST, GET, OPTIONS",
'Access-Control-Allow-Credentials': true, "Access-Control-Allow-Credentials": true,
'Sec-Fetch-Mode': 'no-cors', "Sec-Fetch-Mode": "no-cors",
'Sec-Fetch-Site': 'same-site' "Sec-Fetch-Site": "same-site",
}, },
}; };
try { try {
const response = await fetch(url, requestOptions); const response = await fetch(url, requestOptions);
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error('Error fetching transactions:', error); console.error("Error fetching transactions:", error);
throw error; throw error;
} }
}; };
@@ -431,12 +431,25 @@ export const useFetchInfiniteQuery = ({
} }
return allPages.length + 1; return allPages.length + 1;
}; };
const { data, isLoading, isFetchingNextPage, fetchNextPage, isError, isFetching } = const {
useInfiniteQuery({ data,
isLoading,
isFetchingNextPage,
fetchNextPage,
isError,
isFetching,
} = useInfiniteQuery({
queryKey, queryKey,
queryFn, queryFn,
getNextPageParam, getNextPageParam,
initialPageParam: 1, initialPageParam: 1,
}); });
return { data, isLoading, isFetchingNextPage, fetchNextPage, isError, isFetching }; return {
data,
isLoading,
isFetchingNextPage,
fetchNextPage,
isError,
isFetching,
};
}; };

View File

@@ -1,7 +1,7 @@
import React from 'react' import React from "react";
import ReactDOM from 'react-dom/client' import ReactDOM from "react-dom/client";
import App from './App.tsx' import App from "./App.tsx";
import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
@@ -12,11 +12,10 @@ const queryClient = new QueryClient({
}, },
}); });
ReactDOM.createRoot(document.getElementById('root')!).render( ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode> <React.StrictMode>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<App /> <App />
</QueryClientProvider> </QueryClientProvider>
</React.StrictMode>, </React.StrictMode>,
) );

View File

@@ -1,28 +1,31 @@
import { Stack } from '@mantine/core'; import { Stack } from "@mantine/core";
import stackClasses from '../../styles/Stack.module.css'; import stackClasses from "../../styles/Stack.module.css";
import {useQueryAPI} from '../../hooks/useAPI'; import { useQueryAPI } from "../../hooks/useAPI";
import Search from './components/Search'; import Search from "./components/Search";
import Header from './components/Header'; import Header from "./components/Header";
import Applications from './components/Applications'; import Applications from "./components/Applications";
import Bookmarks from './components/Bookmarks'; import Bookmarks from "./components/Bookmarks";
import SettingsButton from './components/SettingsButton'; import SettingsButton from "./components/SettingsButton";
import { useEffect, useState } from 'react'; import { useEffect, useState } from "react";
import '../../index.css'; import "../../index.css";
import { useLocalStorage } from '@mantine/hooks'; import { useLocalStorage } from "@mantine/hooks";
export default function Homepage({theme}:any) { export default function Homepage({ theme }: any) {
const { data, isFetching } = useQueryAPI({ endpoint: 'api/messages' }); const { data, isFetching } = useQueryAPI({ endpoint: "api/messages" });
const [displayedGreeting, setDisplayedGreeting] = useState(''); const [displayedGreeting, setDisplayedGreeting] = useState("");
const [contentType, setContentType] = useLocalStorage<any>({ key: 'contenttype', defaultValue: 'All' }); const [contentType, setContentType] = useLocalStorage<any>({
key: "contenttype",
defaultValue: "All",
});
function date() { function date() {
const currentDate = new Date(); const currentDate = new Date();
const dateOptions: Intl.DateTimeFormatOptions = { const dateOptions: Intl.DateTimeFormatOptions = {
weekday: 'long', weekday: "long",
year: 'numeric', year: "numeric",
month: 'long', month: "long",
day: 'numeric', day: "numeric",
}; };
const date = currentDate.toLocaleDateString('en-GB', dateOptions); const date = currentDate.toLocaleDateString("en-GB", dateOptions);
return date; return date;
} }
function greet(list: any) { function greet(list: any) {
@@ -32,7 +35,7 @@ export default function Homepage({theme}:any) {
let timeBased: any = []; let timeBased: any = [];
let easterEggs: any = []; let easterEggs: any = [];
for (const l of list) { for (const l of list) {
if (l.easteregg === 'N') { if (l.easteregg === "N") {
timeBased = timeBased.concat(l); timeBased = timeBased.concat(l);
} else { } else {
easterEggs = easterEggs.concat(l); easterEggs = easterEggs.concat(l);
@@ -51,12 +54,16 @@ export default function Homepage({theme}:any) {
}, [isFetching]); }, [isFetching]);
return ( return (
<Stack gap={0} className={stackClasses.containerStack} justify='center'> <Stack gap={0} className={stackClasses.containerStack} justify="center">
<Search /> <Search />
<Header date={date()} greet={displayedGreeting} /> <Header date={date()} greet={displayedGreeting} />
<Applications contentType={contentType}/> <Applications contentType={contentType} />
<Bookmarks contentType={contentType}/> <Bookmarks contentType={contentType} />
<SettingsButton theme={theme} contentType={contentType} setContentType={setContentType}/> <SettingsButton
theme={theme}
contentType={contentType}
setContentType={setContentType}
/>
</Stack> </Stack>
); );
} }

View File

@@ -1,41 +1,43 @@
import { Icon } from '@iconify/react'; import { Icon } from "@iconify/react";
import { Box, Stack, Text } from '@mantine/core'; import { Box, Stack, Text } from "@mantine/core";
import { useState } from 'react'; import { useState } from "react";
import stacks from '../../../styles/Stack.module.css'; import stacks from "../../../styles/Stack.module.css";
import texts from '../../../styles/Text.module.css'; import texts from "../../../styles/Text.module.css";
function AppItem({ url, icon, name }: any) { function AppItem({ url, icon, name }: any) {
const [textDecoration, setTextDecoration] = useState<string>('none'); const [textDecoration, setTextDecoration] = useState<string>("none");
return ( return (
<Box <Box
component='a' component="a"
href={`https://${url}`} href={`https://${url}`}
onMouseOver={() => setTextDecoration('underline')} onMouseOver={() => setTextDecoration("underline")}
onMouseOut={() => setTextDecoration('none')} onMouseOut={() => setTextDecoration("none")}
className={stacks.appItemBox} className={stacks.appItemBox}
> >
<Stack <Stack
style={{ style={{
height: '64px', height: "64px",
marginRight: '1em', marginRight: "1em",
marginLeft: '1em', marginLeft: "1em",
paddingTop: '15px', paddingTop: "15px",
}} }}
> >
<Icon icon={`mdi:${icon}`} height={32} width={32} /> <Icon icon={`mdi:${icon}`} height={32} width={32} />
</Stack> </Stack>
<Stack <Stack
style={{ style={{
display: 'flex', display: "flex",
flexDirection: 'column', flexDirection: "column",
justifyContent: 'center', justifyContent: "center",
flex: 1, flex: 1,
overflow: 'hidden', overflow: "hidden",
gap: 0, gap: 0,
}} }}
> >
<Text className={texts.appName} classNames={texts}>{name}</Text> <Text className={texts.appName} classNames={texts}>
{name}
</Text>
<Text <Text
className={texts.appUrl} className={texts.appUrl}
classNames={texts} classNames={texts}

View File

@@ -1,11 +1,11 @@
import { Stack, Text } from '@mantine/core'; import { Stack, Text } from "@mantine/core";
import { useEffect, useState } from 'react'; import { useEffect, useState } from "react";
import {useQueryAPI} from '../../../hooks/useAPI'; import { useQueryAPI } from "../../../hooks/useAPI";
import texts from '../../../styles/Text.module.css'; import texts from "../../../styles/Text.module.css";
import AppItem from './AppItem'; import AppItem from "./AppItem";
function Applications({contentType}: any) { function Applications({ contentType }: any) {
const { data, isFetching } = useQueryAPI({ endpoint: 'api/apps' }); const { data, isFetching } = useQueryAPI({ endpoint: "api/apps" });
const [applications, setApplications] = useState([]); const [applications, setApplications] = useState([]);
useEffect(() => { useEffect(() => {
if (!isFetching) { if (!isFetching) {
@@ -13,24 +13,26 @@ function Applications({contentType}: any) {
} }
}, [isFetching]); }, [isFetching]);
return ( return (
<Stack id='apps' style={{ gap: 0 }}> <Stack id="apps" style={{ gap: 0 }}>
<Text className={texts.sectionTitle} classNames={texts}>Applications</Text> <Text className={texts.sectionTitle} classNames={texts}>
Applications
</Text>
<Stack <Stack
style={{ style={{
background: 'var(--color-background)', background: "var(--color-background)",
gap: '.25rem', gap: ".25rem",
backgroundSize: 'cover', backgroundSize: "cover",
borderBottom: '0px solid', borderBottom: "0px solid",
display: 'grid', display: "grid",
gridColumnGap: '5px', gridColumnGap: "5px",
gridRowGap: '5px', gridRowGap: "5px",
gridTemplateColumns: '1fr 1fr 1fr 1fr 1fr 1fr', gridTemplateColumns: "1fr 1fr 1fr 1fr 1fr 1fr",
gridTemplateRows: '64px', gridTemplateRows: "64px",
paddingBottom: 'var(--module-spacing)', paddingBottom: "var(--module-spacing)",
}} }}
> >
{applications?.map((item: any) => { {applications?.map((item: any) => {
if (item.category === contentType || contentType === 'All') if (item.category === contentType || contentType === "All")
return ( return (
<AppItem <AppItem
key={item.name} key={item.name}

View File

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

View File

@@ -1,15 +1,15 @@
import { Stack, Text } from '@mantine/core'; import { Stack, Text } from "@mantine/core";
import { useEffect, useState } from 'react'; import { useEffect, useState } from "react";
import {useQueryAPI} from '../../../hooks/useAPI'; import { useQueryAPI } from "../../../hooks/useAPI";
import texts from '../../../styles/Text.module.css'; import texts from "../../../styles/Text.module.css";
import LinkGroups from './LinkGroups'; import LinkGroups from "./LinkGroups";
function Links({contentType}: any) { function Links({ contentType }: any) {
const { data: linkData, isFetched: linksIsFetched } = useQueryAPI({ const { data: linkData, isFetched: linksIsFetched } = useQueryAPI({
endpoint: 'api/links', endpoint: "api/links",
}); });
const { data: groupData, isFetched: groupsIsFetched } = useQueryAPI({ const { data: groupData, isFetched: groupsIsFetched } = useQueryAPI({
endpoint: 'api/categories', endpoint: "api/categories",
}); });
const [bookmarks, setBookmarks] = useState([]); const [bookmarks, setBookmarks] = useState([]);
const [categories, setCategories] = useState([]); const [categories, setCategories] = useState([]);
@@ -24,29 +24,32 @@ function Links({contentType}: any) {
return ( return (
<> <>
{groupsIsFetched && linksIsFetched && categories?.length > 0 && ( {groupsIsFetched && linksIsFetched && categories?.length > 0 && (
<Stack id='links' style={{ gap: 0 }}> <Stack id="links" style={{ gap: 0 }}>
<Text className={texts.sectionTitle} classNames={texts}>Bookmarks</Text> <Text className={texts.sectionTitle} classNames={texts}>
Bookmarks
</Text>
<Stack <Stack
style={{ style={{
display: 'grid', display: "grid",
flexWrap: 'nowrap', flexWrap: "nowrap",
gridColumnGap: '20px', gridColumnGap: "20px",
gridRowGap: '0px', gridRowGap: "0px",
gridTemplateColumns: '1fr 1fr 1fr 1fr 1fr', gridTemplateColumns: "1fr 1fr 1fr 1fr 1fr",
gridTemplateRows: 'auto', gridTemplateRows: "auto",
}} }}
> >
{categories.map((category: any) => { {categories.map((category: any) => {
if ( if (
category.column_filter === contentType || category.column_filter === contentType ||
contentType === 'All' contentType === "All"
) { ) {
return ( return (
<LinkGroups <LinkGroups
key={category.id} key={category.id}
category={category.category_name} category={category.category_name}
links={bookmarks.filter( links={bookmarks.filter(
(bookmark: any) => bookmark.category === category.category_index, (bookmark: any) =>
bookmark.category === category.category_index,
)} )}
/> />
); );

View File

@@ -1,6 +1,6 @@
import { Stack, Text } from '@mantine/core'; import { Stack, Text } from "@mantine/core";
import { useEffect, useState } from 'react'; import { useEffect, useState } from "react";
import {textClasses} from '../../../styles'; import { textClasses } from "../../../styles";
function Header({ greet, date }: any) { function Header({ greet, date }: any) {
const [time, setTime] = useState(new Date()); const [time, setTime] = useState(new Date());
@@ -22,41 +22,47 @@ function Header({ greet, date }: any) {
return ( return (
<Stack <Stack
style={{ style={{
borderBottom: '0px solid', borderBottom: "0px solid",
zIndex: '1', zIndex: "1",
clear: 'both', clear: "both",
display: 'flex', display: "flex",
flexDirection: 'row', flexDirection: "row",
gap: 0, gap: 0,
}} }}
> >
<Stack <Stack
style={{ style={{
display: 'grid', display: "grid",
width: '100%', width: "100%",
}} }}
> >
<Text className={textClasses.greeting} classNames={textClasses}>{greet}</Text> <Text className={textClasses.greeting} classNames={textClasses}>
{greet}
</Text>
</Stack> </Stack>
<Stack <Stack
style={{ style={{
display: 'flex', display: "flex",
float: 'right', float: "right",
justifyContent: 'right', justifyContent: "right",
width: 'auto', width: "auto",
gap: 0, gap: 0,
}} }}
> >
<Text className={textClasses.dateTime} classNames={textClasses} style={{ width: 'max-content' }}> <Text
className={textClasses.dateTime}
classNames={textClasses}
style={{ width: "max-content" }}
>
{date} {date}
</Text> </Text>
<Text <Text
component='h2' component="h2"
className={textClasses.dateTime} className={textClasses.dateTime}
classNames={textClasses} classNames={textClasses}
style={{ style={{
justifyContent: 'right', justifyContent: "right",
display: 'grid', display: "grid",
}} }}
> >
{`${ {`${
@@ -65,7 +71,7 @@ function Header({ greet, date }: any) {
: time.getHours() > 12 : time.getHours() > 12
? time.getHours() - 12 ? time.getHours() - 12
: time.getHours() : time.getHours()
}:${fixDigits(time.getMinutes())} ${time.getHours() >= 12 ? 'PM' : 'AM'}`} }:${fixDigits(time.getMinutes())} ${time.getHours() >= 12 ? "PM" : "AM"}`}
</Text> </Text>
</Stack> </Stack>
</Stack> </Stack>

View File

@@ -1,17 +1,19 @@
import { Stack, Text } from '@mantine/core'; import { Stack, Text } from "@mantine/core";
import texts from '../../../styles/Text.module.css'; import texts from "../../../styles/Text.module.css";
import LinkItem from './LinkItem'; import LinkItem from "./LinkItem";
function LinkGroups({ links, category }: any) { function LinkGroups({ links, category }: any) {
return ( return (
<Stack <Stack
style={{ style={{
marginBottom: '0em', marginBottom: "0em",
display: 'block', display: "block",
lineHeight: '1.5rem', lineHeight: "1.5rem",
}} }}
> >
<Text className={texts.linkCategory} classNames={texts}>{category}</Text> <Text className={texts.linkCategory} classNames={texts}>
{category}
</Text>
{links?.map((link: any) => ( {links?.map((link: any) => (
<LinkItem key={link.name} link={link} /> <LinkItem key={link.name} link={link} />
))} ))}

View File

@@ -1,9 +1,14 @@
import { Text } from '@mantine/core'; import { Text } from "@mantine/core";
import texts from '../../../styles/Text.module.css'; import texts from "../../../styles/Text.module.css";
function LinkItem({ link }: any) { function LinkItem({ link }: any) {
return ( return (
<Text className={texts.linkItem} classNames={texts} component='a' href={link.url}> <Text
className={texts.linkItem}
classNames={texts}
component="a"
href={link.url}
>
{link.name} {link.name}
</Text> </Text>
); );

View File

@@ -1,9 +1,9 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from "react";
import Messages from '../../../components/Messages'; import Messages from "../../../components/Messages";
import useQueryAPI from '../../../hooks/useAPI'; import useQueryAPI from "../../../hooks/useAPI";
function MessageInfo() { function MessageInfo() {
const messageData = useQueryAPI({ endpoint: 'api/messages' }); const messageData = useQueryAPI({ endpoint: "api/messages" });
const [greetings, setGreetings] = useState([]); const [greetings, setGreetings] = useState([]);
useEffect(() => { useEffect(() => {
@@ -15,7 +15,7 @@ function MessageInfo() {
return ( return (
<> <>
{greetings && ( {greetings && (
<div id='messageForm'> <div id="messageForm">
{greetings.map((greeting, index) => ( {greetings.map((greeting, index) => (
<Messages <Messages
key={greeting.id} key={greeting.id}

View File

@@ -4,35 +4,40 @@ import { useQueryAPI } from "../../../hooks";
import { useState } from "react"; import { useState } from "react";
export default function Search() { export default function Search() {
const { data: providers } = useQueryAPI({ endpoint: 'api/providers' }); const { data: providers } = useQueryAPI({ endpoint: "api/providers" });
const [providerLabel, setProviderLabel] = useState('Google Search'); const [providerLabel, setProviderLabel] = useState("Google Search");
const [providerURL, setProviderURL] = useState('https://www.google.com/search?q='); const [providerURL, setProviderURL] = useState(
"https://www.google.com/search?q=",
);
const handleKeyPress = (item: any) => { const handleKeyPress = (item: any) => {
const option = item.target.value.substr(1, item.target.value.indexOf(' ') - 1) || item.target.value.substr(1); 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); const subtext = item.target.value.substr(2 + option.length);
if (item.key === 'Enter') { if (item.key === "Enter") {
if (item.target.value.startsWith('/')) { if (item.target.value.startsWith("/")) {
open(`${providerURL}${subtext}`, '_self'); open(`${providerURL}${subtext}`, "_self");
} else if (validURL(item.target.value)) { } else if (validURL(item.target.value)) {
if (containsProtocol(item.target.value)) window.location = item.target.value; if (containsProtocol(item.target.value))
else open(`https://${item.target.value}`, '_self'); window.location = item.target.value;
else open(`https://${item.target.value}`, "_self");
} else { } else {
open(`${providerURL}${subtext}`, '_self'); open(`${providerURL}${subtext}`, "_self");
}
} }
} }
};
function validURL(str: any) { function validURL(str: any) {
const pattern = new RegExp( const pattern = new RegExp(
'^(https?:\\/\\/)?' + // protocol "^(https?:\\/\\/)?" + // protocol
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name "((([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{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path "(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path
'(\\?[;&a-z\\d%_.~+=-]*)?' + // query string "(\\?[;&a-z\\d%_.~+=-]*)?" + // query string
'(\\#[-a-z\\d_]*)?$', "(\\#[-a-z\\d_]*)?$",
'i', "i",
); // fragment locator ); // fragment locator
return !!pattern.test(str); return !!pattern.test(str);
} }
@@ -44,26 +49,34 @@ export default function Search() {
return ( return (
<Stack <Stack
style={{ style={{
backgroundColor: 'transparent', backgroundColor: "transparent",
textAlign: 'center', textAlign: "center",
position: 'relative', position: "relative",
padding: '.3em', padding: ".3em",
display: 'inline-block', display: "inline-block",
gap: 0, gap: 0,
}} }}
> >
<TextInput <TextInput
className={textInputClasses.search} className={textInputClasses.search}
classNames={textInputClasses} classNames={textInputClasses}
spellCheck='true' spellCheck="true"
leftSection={providerLabel} leftSection={providerLabel}
onChange={(item: any) => { onChange={(item: any) => {
if(item.target.value.startsWith('/')) { if (item.target.value.startsWith("/")) {
setProviderLabel(providers?.find((p: any) => p.prefix === item.target.value.split(' ')[0]).name); setProviderLabel(
setProviderURL(providers?.find((p: any) => p.prefix === item.target.value.split(' ')[0]).url); 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 { } else {
setProviderLabel('Google Search'); setProviderLabel("Google Search");
setProviderURL('https://www.google.com/search?q='); setProviderURL("https://www.google.com/search?q=");
} }
}} }}
onKeyUp={(item: any) => { onKeyUp={(item: any) => {

View File

@@ -1,58 +1,58 @@
import { Button, Menu, NavLink, Select } from '@mantine/core'; import { Button, Menu, NavLink, Select } from "@mantine/core";
import { useEffect, useState } from 'react'; import { useEffect, useState } from "react";
import { FiMenu, FiMonitor } from 'react-icons/fi'; import { FiMenu, FiMonitor } from "react-icons/fi";
import {useQueryAPI} from '../../../hooks/useAPI'; import { useQueryAPI } from "../../../hooks/useAPI";
import button from '../../../styles/Button.module.css'; import button from "../../../styles/Button.module.css";
import menu from '../../../styles/Menu.module.css'; import menu from "../../../styles/Menu.module.css";
import navLink from '../../../styles/NavLink.module.css'; import navLink from "../../../styles/NavLink.module.css";
import selectClasses from '../../../styles/Select.module.css'; import selectClasses from "../../../styles/Select.module.css";
function SettingsButton({contentType, setContentType, theme}: any) { function SettingsButton({ contentType, setContentType, theme }: any) {
const { data, isFetched } = useQueryAPI({ endpoint: 'api/appCategories' }); const { data, isFetched } = useQueryAPI({ endpoint: "api/appCategories" });
const [categories, setCategories] = useState([{value: 'All', label: 'All'}]); const [categories, setCategories] = useState([
{ value: "All", label: "All" },
]);
useEffect(() => { useEffect(() => {
if(data) { if (data) {
const list: [any] = data.map((category: any) => {return {value: category.category, label: category.category}}) || []; const list: [any] =
list.unshift({value: 'All', label: 'All'}); data.map((category: any) => {
return { value: category.category, label: category.category };
}) || [];
list.unshift({ value: "All", label: "All" });
setCategories(list); setCategories(list);
} }
}, [data, isFetched]); }, [data, isFetched]);
function runWakeCommand() { function runWakeCommand() {
const requestOptions = { const requestOptions = {
method: 'GET', method: "GET",
headers: { 'Content-Type': 'application/json' }, headers: { "Content-Type": "application/json" },
}; };
fetch('/trigger/api/wakeDakota', requestOptions); fetch("/trigger/api/wakeDakota", requestOptions);
} }
return ( return (
<Menu <Menu withinPortal={false} withArrow shadow="md" position="top">
withinPortal={false}
withArrow
shadow='md'
position='top'
>
<Menu.Target> <Menu.Target>
<Button className={button.settingsButton} classNames={button}> <Button className={button.settingsButton} classNames={button}>
<FiMenu /> <FiMenu />
</Button> </Button>
</Menu.Target> </Menu.Target>
<Menu.Dropdown <Menu.Dropdown className={menu.dropdown}>
className={menu.dropdown}
>
<NavLink <NavLink
label='Settings' label="Settings"
component='a' component="a"
href='/trigger/settings' href="/trigger/settings"
className={navLink.defaultNavLink} className={navLink.defaultNavLink}
classNames={navLink} classNames={navLink}
/> />
<NavLink label='Quick Actions'
className={navLink.defaultNavLink}
classNames={navLink}>
<NavLink <NavLink
label='Wake Dakota' label="Quick Actions"
className={navLink.defaultNavLink}
classNames={navLink}
>
<NavLink
label="Wake Dakota"
leftSection={<FiMonitor />} leftSection={<FiMonitor />}
onClick={() => runWakeCommand()} onClick={() => runWakeCommand()}
className={navLink.defaultNavLink} className={navLink.defaultNavLink}
@@ -60,12 +60,12 @@ function SettingsButton({contentType, setContentType, theme}: any) {
/> />
</NavLink> </NavLink>
<Select <Select
label='Homepage Filter' label="Homepage Filter"
data={categories ?? []} data={categories ?? []}
defaultValue={contentType} defaultValue={contentType}
withCheckIcon={false} withCheckIcon={false}
onChange={(data: any) => { onChange={(data: any) => {
setContentType(data) setContentType(data);
}} }}
classNames={selectClasses} classNames={selectClasses}
comboboxProps={{ withinPortal: false }} comboboxProps={{ withinPortal: false }}

View File

@@ -1,8 +1,8 @@
import { ActionIcon, Group, Stack, Tabs } from "@mantine/core"; import { ActionIcon, Group, Stack, Tabs, Text } from "@mantine/core";
import tabClasses from '../../styles/Tabs.module.css'; import tabClasses from "../../styles/Tabs.module.css";
import Personalization from "./components/Personalization"; import Personalization from "./components/Personalization";
import { FiArrowLeftCircle } from "react-icons/fi"; import { FiArrowLeftCircle } from "react-icons/fi";
import { actionIconClasses } from "../../styles"; import { actionIconClasses, textClasses } from "../../styles";
import Messages from "./components/Messages"; import Messages from "./components/Messages";
import Applications from "./components/Applications"; import Applications from "./components/Applications";
import Bookmarks from "./components/Bookmarks"; import Bookmarks from "./components/Bookmarks";
@@ -10,14 +10,28 @@ import Categories from "./components/Categories";
import SearchProviders from "./components/SearchProviders"; import SearchProviders from "./components/SearchProviders";
import Themes from "./components/Themes"; import Themes from "./components/Themes";
function Settings({theme}:any) { function Settings({ theme }: any) {
return ( return (
<Stack h={'100vh'} px={'2rem'} bg={'var(--color-background)'}> <Stack h={"100vh"} px={"2rem"} bg={"var(--color-background)"}>
<Group gap={0} > <Group gap={0}>
<ActionIcon className={actionIconClasses.backButton} classNames={actionIconClasses} component="a" href="/trigger"><FiArrowLeftCircle/></ActionIcon> <ActionIcon
<h1>Settings</h1> className={actionIconClasses.backButton}
classNames={actionIconClasses}
component="a"
href="/trigger"
>
<FiArrowLeftCircle />
</ActionIcon>
<Text className={textClasses.settingsTitle} classNames={textClasses}>
Settings
</Text>
</Group> </Group>
<Tabs orientation="vertical" defaultValue={'personalization'} className={tabClasses.settingsTabs} classNames={tabClasses}> <Tabs
orientation="vertical"
defaultValue={"personalization"}
className={tabClasses.settingsTabs}
classNames={tabClasses}
>
<Tabs.List> <Tabs.List>
<Tabs.Tab value="personalization">Personal Info</Tabs.Tab> <Tabs.Tab value="personalization">Personal Info</Tabs.Tab>
<Tabs.Tab value="messages">Messages</Tabs.Tab> <Tabs.Tab value="messages">Messages</Tabs.Tab>
@@ -27,13 +41,27 @@ function Settings({theme}:any) {
<Tabs.Tab value="themes">Themes</Tabs.Tab> <Tabs.Tab value="themes">Themes</Tabs.Tab>
<Tabs.Tab value="providers">Search Providers</Tabs.Tab> <Tabs.Tab value="providers">Search Providers</Tabs.Tab>
</Tabs.List> </Tabs.List>
<Tabs.Panel value="personalization"><Personalization/></Tabs.Panel> <Tabs.Panel value="personalization">
<Tabs.Panel value="messages"><Messages/></Tabs.Panel> <Personalization />
<Tabs.Panel value="applications"><Applications/></Tabs.Panel> </Tabs.Panel>
<Tabs.Panel value="bookmarks"><Bookmarks/></Tabs.Panel> <Tabs.Panel value="messages">
<Tabs.Panel value="bookmarksCategories"><Categories/></Tabs.Panel> <Messages />
<Tabs.Panel value="themes"><Themes/></Tabs.Panel> </Tabs.Panel>
<Tabs.Panel value="providers"><SearchProviders/></Tabs.Panel> <Tabs.Panel value="applications">
<Applications />
</Tabs.Panel>
<Tabs.Panel value="bookmarks">
<Bookmarks />
</Tabs.Panel>
<Tabs.Panel value="bookmarksCategories">
<Categories />
</Tabs.Panel>
<Tabs.Panel value="themes">
<Themes theme={theme} />
</Tabs.Panel>
<Tabs.Panel value="providers">
<SearchProviders />
</Tabs.Panel>
</Tabs> </Tabs>
</Stack> </Stack>
); );

View File

@@ -1,10 +1,11 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import {useMutationAPI, useQueryAPI} from "../../../hooks/useAPI"; import { useMutationAPI, useQueryAPI } from "../../../hooks/useAPI";
import { DataTable } from "../../../DataTable/DataTable"; import { DataTable } from "../../../DataTable/DataTable";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import type { MRT_TableOptions } from "mantine-react-table"; import type { MRT_TableOptions } from "mantine-react-table";
import ApplicationColumns from "../../../DataTable/columns/ApplicationColumns"; import ApplicationColumns from "../../../DataTable/columns/ApplicationColumns";
import { Stack } from "@mantine/core"; import { Stack, Text } from "@mantine/core";
import { textClasses } from "../../../styles";
const ApplicationSettings = () => { const ApplicationSettings = () => {
const endpoint = "api/apps"; const endpoint = "api/apps";
const { data, isFetching, isLoading, isFetched } = useQueryAPI({ const { data, isFetching, isLoading, isFetched } = useQueryAPI({
@@ -12,30 +13,29 @@ const ApplicationSettings = () => {
}); });
const [applicationData, setApplicationData] = useState<any>([]); const [applicationData, setApplicationData] = useState<any>([]);
const {mutateAsync: createRow, isPending: isCreatingRow} = useMutationAPI(); const { mutateAsync: createRow, isPending: isCreatingRow } = useMutationAPI();
const {mutateAsync: editRow, isPending: isEditingRow} = useMutationAPI(); const { mutateAsync: editRow, isPending: isEditingRow } = useMutationAPI();
useEffect(() => { useEffect(() => {
if(isFetched) { if (isFetched) {
setApplicationData(data); setApplicationData(data);
} }
}) });
const handleCreateRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({ const handleCreateRow: MRT_TableOptions<any>["onCreatingRowSave"] = async ({
values, values,
exitCreatingMode, exitCreatingMode,
}) => { }) => {
createRow({ method: "POST", endpoint: endpoint, body: values }).then(() => {
createRow({method: "POST", endpoint: endpoint, body: values}).then(() => {
toast.success("Application created successfully"); toast.success("Application created successfully");
}) });
exitCreatingMode(); exitCreatingMode();
}; };
const handleEditRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({ const handleEditRow: MRT_TableOptions<any>["onCreatingRowSave"] = async ({
values, values,
table, table,
}) => { }) => {
editRow({method: "PATCH", endpoint: endpoint, body: values}).then(() => { editRow({ method: "PATCH", endpoint: endpoint, body: values }).then(() => {
toast.success("Application updated successfully"); toast.success("Application updated successfully");
}); });
table.setEditingRow(null); table.setEditingRow(null);
@@ -43,7 +43,9 @@ const ApplicationSettings = () => {
return ( return (
<Stack> <Stack>
<h2>Applications</h2> <Text className={textClasses.settingsSubTitle} classNames={textClasses}>
Applications
</Text>
<DataTable <DataTable
tableName="Applications" tableName="Applications"
columns={ApplicationColumns()} columns={ApplicationColumns()}
@@ -54,6 +56,6 @@ const ApplicationSettings = () => {
onEditingRowSave={handleEditRow} onEditingRowSave={handleEditRow}
/> />
</Stack> </Stack>
) );
}; };
export default ApplicationSettings; export default ApplicationSettings;

View File

@@ -1,10 +1,11 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import {useMutationAPI, useQueryAPI} from "../../../hooks/useAPI"; import { useMutationAPI, useQueryAPI } from "../../../hooks/useAPI";
import { DataTable } from "../../../DataTable/DataTable"; import { DataTable } from "../../../DataTable/DataTable";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import type { MRT_TableOptions } from "mantine-react-table"; import type { MRT_TableOptions } from "mantine-react-table";
import { Stack } from "@mantine/core"; import { Stack, Text } from "@mantine/core";
import BookmarkColumns from "../../../DataTable/columns/BookmarkColumns"; import BookmarkColumns from "../../../DataTable/columns/BookmarkColumns";
import { textClasses } from "../../../styles";
const BookmarkSettings = () => { const BookmarkSettings = () => {
const endpoint = "api/links"; const endpoint = "api/links";
const { data, isFetching, isLoading, isFetched } = useQueryAPI({ const { data, isFetching, isLoading, isFetched } = useQueryAPI({
@@ -19,14 +20,14 @@ const BookmarkSettings = () => {
const [bookmarkData, setBookmarkData] = useState<any>([]); const [bookmarkData, setBookmarkData] = useState<any>([]);
const [groups, setGroups] = useState([]); const [groups, setGroups] = useState([]);
const [fullyFetched, setFullyFetched] = useState(false); const [fullyFetched, setFullyFetched] = useState(false);
const {mutateAsync: createRow, isPending: isCreatingRow} = useMutationAPI(); const { mutateAsync: createRow, isPending: isCreatingRow } = useMutationAPI();
const {mutateAsync: editRow, isPending: isEditingRow} = useMutationAPI(); const { mutateAsync: editRow, isPending: isEditingRow } = useMutationAPI();
useEffect(() => { useEffect(() => {
if(isFetched) { if (isFetched) {
setBookmarkData(data); setBookmarkData(data);
} }
}) });
useEffect(() => { useEffect(() => {
if (groupDataisFetched) { if (groupDataisFetched) {
@@ -38,21 +39,20 @@ const BookmarkSettings = () => {
} }
}, [groupDataisFetched]); }, [groupDataisFetched]);
const handleCreateRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({ const handleCreateRow: MRT_TableOptions<any>["onCreatingRowSave"] = async ({
values, values,
exitCreatingMode, exitCreatingMode,
}) => { }) => {
createRow({ method: "POST", endpoint: endpoint, body: values }).then(() => {
createRow({method: "POST", endpoint: endpoint, body: values}).then(() => {
toast.success("Application created successfully"); toast.success("Application created successfully");
}) });
exitCreatingMode(); exitCreatingMode();
}; };
const handleEditRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({ const handleEditRow: MRT_TableOptions<any>["onCreatingRowSave"] = async ({
values, values,
table, table,
}) => { }) => {
editRow({method: "PATCH", endpoint: endpoint, body: values}).then(() => { editRow({ method: "PATCH", endpoint: endpoint, body: values }).then(() => {
toast.success("Application updated successfully"); toast.success("Application updated successfully");
}); });
table.setEditingRow(null); table.setEditingRow(null);
@@ -60,7 +60,9 @@ const BookmarkSettings = () => {
return ( return (
<Stack> <Stack>
<h2>Bookmarks</h2> <Text className={textClasses.settingsSubTitle} classNames={textClasses}>
Bookmarks
</Text>
<DataTable <DataTable
tableName="Applications" tableName="Applications"
columns={BookmarkColumns(groups, groupData)} columns={BookmarkColumns(groups, groupData)}
@@ -71,6 +73,6 @@ const BookmarkSettings = () => {
onEditingRowSave={handleEditRow} onEditingRowSave={handleEditRow}
/> />
</Stack> </Stack>
) );
}; };
export default BookmarkSettings; export default BookmarkSettings;

View File

@@ -1,10 +1,11 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import {useMutationAPI, useQueryAPI} from "../../../hooks/useAPI"; import { useMutationAPI, useQueryAPI } from "../../../hooks/useAPI";
import { DataTable } from "../../../DataTable/DataTable"; import { DataTable } from "../../../DataTable/DataTable";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import type { MRT_TableOptions } from "mantine-react-table"; import type { MRT_TableOptions } from "mantine-react-table";
import CategoryColumns from "../../../DataTable/columns/CategoryColumns"; import CategoryColumns from "../../../DataTable/columns/CategoryColumns";
import { Stack } from "@mantine/core"; import { Stack, Text } from "@mantine/core";
import { textClasses } from "../../../styles";
const CategorySettings = () => { const CategorySettings = () => {
const endpoint = "api/categories"; const endpoint = "api/categories";
const { data, isFetching, isLoading, isFetched } = useQueryAPI({ const { data, isFetching, isLoading, isFetched } = useQueryAPI({
@@ -12,30 +13,29 @@ const CategorySettings = () => {
}); });
const [categoryData, setCategoryData] = useState<any>([]); const [categoryData, setCategoryData] = useState<any>([]);
const {mutateAsync: createRow, isPending: isCreatingRow} = useMutationAPI(); const { mutateAsync: createRow, isPending: isCreatingRow } = useMutationAPI();
const {mutateAsync: editRow, isPending: isEditingRow} = useMutationAPI(); const { mutateAsync: editRow, isPending: isEditingRow } = useMutationAPI();
useEffect(() => { useEffect(() => {
if(isFetched) { if (isFetched) {
setCategoryData(data); setCategoryData(data);
} }
}) });
const handleCreateRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({ const handleCreateRow: MRT_TableOptions<any>["onCreatingRowSave"] = async ({
values, values,
exitCreatingMode, exitCreatingMode,
}) => { }) => {
createRow({ method: "POST", endpoint: endpoint, body: values }).then(() => {
createRow({method: "POST", endpoint: endpoint, body: values}).then(() => {
toast.success("Category created successfully"); toast.success("Category created successfully");
}) });
exitCreatingMode(); exitCreatingMode();
}; };
const handleEditRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({ const handleEditRow: MRT_TableOptions<any>["onCreatingRowSave"] = async ({
values, values,
table, table,
}) => { }) => {
editRow({method: "PATCH", endpoint: endpoint, body: values}).then(() => { editRow({ method: "PATCH", endpoint: endpoint, body: values }).then(() => {
toast.success("Category updated successfully"); toast.success("Category updated successfully");
}); });
table.setEditingRow(null); table.setEditingRow(null);
@@ -43,7 +43,9 @@ const CategorySettings = () => {
return ( return (
<Stack> <Stack>
<h2>Categories</h2> <Text className={textClasses.settingsSubTitle} classNames={textClasses}>
Categories
</Text>
<DataTable <DataTable
tableName="Categories" tableName="Categories"
columns={CategoryColumns()} columns={CategoryColumns()}
@@ -54,6 +56,6 @@ const CategorySettings = () => {
onEditingRowSave={handleEditRow} onEditingRowSave={handleEditRow}
/> />
</Stack> </Stack>
) );
}; };
export default CategorySettings; export default CategorySettings;

View File

@@ -1,10 +1,11 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import {useMutationAPI, useQueryAPI} from "../../../hooks/useAPI"; import { useMutationAPI, useQueryAPI } from "../../../hooks/useAPI";
import { DataTable } from "../../../DataTable/DataTable"; import { DataTable } from "../../../DataTable/DataTable";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import type { MRT_TableOptions } from "mantine-react-table"; import type { MRT_TableOptions } from "mantine-react-table";
import MessagesColumns from "../../../DataTable/columns/MessagesColumns"; import MessagesColumns from "../../../DataTable/columns/MessagesColumns";
import { Stack } from "@mantine/core"; import { Stack, Text } from "@mantine/core";
import { textClasses } from "../../../styles";
const MessageSettings = () => { const MessageSettings = () => {
const endpoint = "api/messages"; const endpoint = "api/messages";
const { data, isFetching, isLoading, isFetched } = useQueryAPI({ const { data, isFetching, isLoading, isFetched } = useQueryAPI({
@@ -12,30 +13,29 @@ const MessageSettings = () => {
}); });
const [messageData, setMessageData] = useState<any>([]); const [messageData, setMessageData] = useState<any>([]);
const {mutateAsync: createRow, isPending: isCreatingRow} = useMutationAPI(); const { mutateAsync: createRow, isPending: isCreatingRow } = useMutationAPI();
const {mutateAsync: editRow, isPending: isEditingRow} = useMutationAPI(); const { mutateAsync: editRow, isPending: isEditingRow } = useMutationAPI();
useEffect(() => { useEffect(() => {
if(isFetched) { if (isFetched) {
setMessageData(data); setMessageData(data);
} }
}) });
const handleCreateRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({ const handleCreateRow: MRT_TableOptions<any>["onCreatingRowSave"] = async ({
values, values,
exitCreatingMode, exitCreatingMode,
}) => { }) => {
createRow({ method: "POST", endpoint: endpoint, body: values }).then(() => {
createRow({method: "POST", endpoint: endpoint, body: values}).then(() => {
toast.success("Message created successfully"); toast.success("Message created successfully");
}) });
exitCreatingMode(); exitCreatingMode();
}; };
const handleEditRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({ const handleEditRow: MRT_TableOptions<any>["onCreatingRowSave"] = async ({
values, values,
table, table,
}) => { }) => {
editRow({method: "PATCH", endpoint: endpoint, body: values}).then(() => { editRow({ method: "PATCH", endpoint: endpoint, body: values }).then(() => {
toast.success("Message updated successfully"); toast.success("Message updated successfully");
}); });
table.setEditingRow(null); table.setEditingRow(null);
@@ -43,7 +43,9 @@ const MessageSettings = () => {
return ( return (
<Stack> <Stack>
<h2>Messages</h2> <Text className={textClasses.settingsSubTitle} classNames={textClasses}>
Messages
</Text>
<DataTable <DataTable
tableName="Messages" tableName="Messages"
columns={MessagesColumns()} columns={MessagesColumns()}
@@ -54,6 +56,6 @@ const MessageSettings = () => {
onEditingRowSave={handleEditRow} onEditingRowSave={handleEditRow}
/> />
</Stack> </Stack>
) );
}; };
export default MessageSettings; export default MessageSettings;

View File

@@ -1,9 +1,11 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import {useMutationAPI, useQueryAPI} from "../../../hooks/useAPI"; import { useMutationAPI, useQueryAPI } from "../../../hooks/useAPI";
import { DataTable } from "../../../DataTable/DataTable"; import { DataTable } from "../../../DataTable/DataTable";
import PersonalizationColumns from "../../../DataTable/columns/PersonalizationColumns"; import PersonalizationColumns from "../../../DataTable/columns/PersonalizationColumns";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import type { MRT_TableOptions } from "mantine-react-table"; import type { MRT_TableOptions } from "mantine-react-table";
import { textClasses } from "../../../styles";
import { Text } from "@mantine/core";
const PersonalizationSettings = () => { const PersonalizationSettings = () => {
const endpoint = "api/personaldata"; const endpoint = "api/personaldata";
const { data, isFetching, isLoading, isFetched } = useQueryAPI({ const { data, isFetching, isLoading, isFetched } = useQueryAPI({
@@ -11,30 +13,29 @@ const PersonalizationSettings = () => {
}); });
const [personalData, setPersonalData] = useState<any>([]); const [personalData, setPersonalData] = useState<any>([]);
const {mutateAsync: createRow, isPending: isCreatingRow} = useMutationAPI(); const { mutateAsync: createRow, isPending: isCreatingRow } = useMutationAPI();
const {mutateAsync: editRow, isPending: isEditingRow} = useMutationAPI(); const { mutateAsync: editRow, isPending: isEditingRow } = useMutationAPI();
useEffect(() => { useEffect(() => {
if(isFetched) { if (isFetched) {
setPersonalData(data); setPersonalData(data);
} }
}) });
const handleCreateRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({ const handleCreateRow: MRT_TableOptions<any>["onCreatingRowSave"] = async ({
values, values,
exitCreatingMode, exitCreatingMode,
}) => { }) => {
createRow({ method: "POST", endpoint: endpoint, body: values }).then(() => {
createRow({method: "POST", endpoint: endpoint, body: values}).then(() => {
toast.success("Personalization created successfully"); toast.success("Personalization created successfully");
}) });
exitCreatingMode(); exitCreatingMode();
}; };
const handleEditRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({ const handleEditRow: MRT_TableOptions<any>["onCreatingRowSave"] = async ({
values, values,
table, table,
}) => { }) => {
editRow({method: "PATCH", endpoint: endpoint, body: values}).then(() => { editRow({ method: "PATCH", endpoint: endpoint, body: values }).then(() => {
toast.success("Personalization updated successfully"); toast.success("Personalization updated successfully");
}); });
table.setEditingRow(null); table.setEditingRow(null);
@@ -42,7 +43,9 @@ const PersonalizationSettings = () => {
return ( return (
<> <>
<h2>Personalization</h2> <Text className={textClasses.settingsSubTitle} classNames={textClasses}>
Personalization
</Text>
<DataTable <DataTable
tableName="Personalization" tableName="Personalization"
columns={PersonalizationColumns()} columns={PersonalizationColumns()}
@@ -53,6 +56,6 @@ const PersonalizationSettings = () => {
onEditingRowSave={handleEditRow} onEditingRowSave={handleEditRow}
/> />
</> </>
) );
}; };
export default PersonalizationSettings; export default PersonalizationSettings;

View File

@@ -1,10 +1,11 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import {useMutationAPI, useQueryAPI} from "../../../hooks/useAPI"; import { useMutationAPI, useQueryAPI } from "../../../hooks/useAPI";
import { DataTable } from "../../../DataTable/DataTable"; import { DataTable } from "../../../DataTable/DataTable";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import type { MRT_TableOptions } from "mantine-react-table"; import type { MRT_TableOptions } from "mantine-react-table";
import SearchProviderColumns from "../../../DataTable/columns/SearchProviderColumns"; import SearchProviderColumns from "../../../DataTable/columns/SearchProviderColumns";
import { Stack } from "@mantine/core"; import { Stack, Text } from "@mantine/core";
import { textClasses } from "../../../styles";
const SearchProviderSettings = () => { const SearchProviderSettings = () => {
const endpoint = "api/providers"; const endpoint = "api/providers";
const { data, isFetching, isLoading, isFetched } = useQueryAPI({ const { data, isFetching, isLoading, isFetched } = useQueryAPI({
@@ -12,30 +13,29 @@ const SearchProviderSettings = () => {
}); });
const [providerData, setProviderData] = useState<any>([]); const [providerData, setProviderData] = useState<any>([]);
const {mutateAsync: createRow, isPending: isCreatingRow} = useMutationAPI(); const { mutateAsync: createRow, isPending: isCreatingRow } = useMutationAPI();
const {mutateAsync: editRow, isPending: isEditingRow} = useMutationAPI(); const { mutateAsync: editRow, isPending: isEditingRow } = useMutationAPI();
useEffect(() => { useEffect(() => {
if(isFetched) { if (isFetched) {
setProviderData(data); setProviderData(data);
} }
}) });
const handleCreateRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({ const handleCreateRow: MRT_TableOptions<any>["onCreatingRowSave"] = async ({
values, values,
exitCreatingMode, exitCreatingMode,
}) => { }) => {
createRow({ method: "POST", endpoint: endpoint, body: values }).then(() => {
createRow({method: "POST", endpoint: endpoint, body: values}).then(() => {
toast.success("Search provider created successfully"); toast.success("Search provider created successfully");
}) });
exitCreatingMode(); exitCreatingMode();
}; };
const handleEditRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({ const handleEditRow: MRT_TableOptions<any>["onCreatingRowSave"] = async ({
values, values,
table, table,
}) => { }) => {
editRow({method: "PATCH", endpoint: endpoint, body: values}).then(() => { editRow({ method: "PATCH", endpoint: endpoint, body: values }).then(() => {
toast.success("Search provider updated successfully"); toast.success("Search provider updated successfully");
}); });
table.setEditingRow(null); table.setEditingRow(null);
@@ -43,7 +43,9 @@ const SearchProviderSettings = () => {
return ( return (
<Stack> <Stack>
<h2>Search Providers</h2> <Text className={textClasses.settingsSubTitle} classNames={textClasses}>
Search Providers
</Text>
<DataTable <DataTable
tableName="Categories" tableName="Categories"
columns={SearchProviderColumns()} columns={SearchProviderColumns()}
@@ -54,6 +56,6 @@ const SearchProviderSettings = () => {
onEditingRowSave={handleEditRow} onEditingRowSave={handleEditRow}
/> />
</Stack> </Stack>
) );
}; };
export default SearchProviderSettings; export default SearchProviderSettings;

View File

@@ -0,0 +1,250 @@
import { Icon } from "@iconify/react/dist/iconify.js";
import { Stack, Group, Text, Box } from "@mantine/core";
import { useState } from "react";
import { stackClasses } from "../../../styles";
export default function ThemeModalContent({ selectedTheme }: any) {
const ExampleApp = (previewTheme: any) => {
const [textDecoration, setTextDecoration] = useState<string>("none");
return (
<Box
component="a"
onMouseOver={() => setTextDecoration("underline")}
onMouseOut={() => setTextDecoration("none")}
className={stackClasses.appItemBox}
>
<Stack
style={{
height: "64px",
marginRight: "1em",
marginLeft: "1em",
paddingTop: "15px",
}}
>
<Icon
style={{ color: previewTheme.theme.colortextpri }}
icon={`mdi:check`}
height={32}
width={32}
/>
</Stack>
<Stack
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
flex: 1,
overflow: "hidden",
gap: 0,
}}
>
<Text
style={{
fontWeight: 400,
fontSize: 11,
fontStyle: "normal",
color: previewTheme.theme.colortextpri,
fontFamily:
"-apple-system, Jost, BlinkMacSystemFont, Helvetica Neue, Roboto, sans-serif !important",
margin: 0,
}}
>
{"Example App"}
</Text>
<Text
style={{
fontWeight: 400,
fontSize: 11,
fontStyle: "italic",
color: previewTheme.theme.colortextacc,
fontFamily:
"-apple-system, Jost, BlinkMacSystemFont, Helvetica Neue, Roboto, sans-serif !important",
margin: 0,
textDecoration: textDecoration,
}}
>
{"https://www.example.com"}
</Text>
</Stack>
</Box>
);
};
return (
<Stack
style={{
width: "100%",
backgroundColor: selectedTheme.colorbackground,
borderRadius: "10px",
}}
>
<Group
justify="center"
style={{
color: selectedTheme.colortextpri,
paddingTop: "1rem",
}}
>
{selectedTheme.name}
</Group>
<Text
pl={"1.5rem"}
style={{
textDecoration: "underline",
fontSize: "20px",
fontWeight: 200,
fontStyle: "italic",
height: "10px",
textTransform: "uppercase",
marginTop: "20px",
marginBottom: "20px",
color: selectedTheme.colortextpri,
}}
>
Applications
</Text>
<Stack
style={{
background: selectedTheme.colorbackground,
gap: ".25rem",
backgroundSize: "cover",
borderBottom: "0px solid",
display: "grid",
gridColumnGap: "5px",
gridRowGap: "5px",
gridTemplateColumns: "1fr 1fr 1fr",
gridTemplateRows: "64px",
padding: "1rem",
paddingTop: "0rem",
justifyContent: "center",
alignContent: "center",
}}
>
<ExampleApp theme={selectedTheme} />
<ExampleApp theme={selectedTheme} />
<ExampleApp theme={selectedTheme} />
</Stack>
<Text
pl={"1.5rem"}
style={{
textDecoration: "underline",
fontSize: "20px",
fontWeight: 200,
fontStyle: "italic",
height: "10px",
textTransform: "uppercase",
marginTop: "20px",
marginBottom: "20px",
color: selectedTheme.colortextpri,
}}
>
Bookmarks
</Text>
<Stack
style={{
display: "flex",
flexDirection: "row",
flexWrap: "nowrap",
gridColumnGap: "20px",
gridRowGap: "0px",
gridTemplateColumns: "1fr 1fr 1fr 1fr 1fr",
gridTemplateRows: "auto",
}}
>
<Stack
style={{
width: "100%",
marginBottom: "0em",
display: "block",
lineHeight: "1.5rem",
padding: "1rem",
paddingTop: "0rem",
}}
>
<Text
style={{
fontSize: "14px",
fontWeight: 400,
height: "10px",
width: "auto",
color: selectedTheme.colortextacc,
textTransform: "uppercase",
marginTop: "20px",
marginBottom: "20px",
}}
>
Example Category
</Text>
<Text
style={{
display: "block",
fontSize: "14px",
lineHeight: 2,
color: selectedTheme.colortextpri,
}}
component="a"
>
{"Link 1"}
</Text>
<Text
style={{
display: "block",
fontSize: "14px",
lineHeight: 2,
color: selectedTheme.colortextpri,
}}
component="a"
>
{"Link 2"}
</Text>
</Stack>
<Stack
style={{
width: "100%",
marginBottom: "0em",
display: "block",
lineHeight: "1.5rem",
padding: "1rem",
paddingTop: "0rem",
}}
>
<Text
style={{
fontSize: "14px",
fontWeight: 400,
height: "10px",
width: "auto",
color: selectedTheme.colortextacc,
textTransform: "uppercase",
marginTop: "20px",
marginBottom: "20px",
}}
>
Example Category
</Text>
<Text
style={{
display: "block",
fontSize: "14px",
lineHeight: 2,
color: selectedTheme.colortextpri,
}}
component="a"
>
{"Link 1"}
</Text>
<Text
style={{
display: "block",
fontSize: "14px",
lineHeight: 2,
color: selectedTheme.colortextpri,
}}
component="a"
>
{"Link 2"}
</Text>
</Stack>
</Stack>
</Stack>
);
}

View File

@@ -1,14 +1,32 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useQueryAPI } from "../../../hooks/useAPI"; import { useQueryAPI } from "../../../hooks/useAPI";
import { ActionIcon, Button, ColorPicker, ColorSwatch, Group, Modal, SimpleGrid, Stack } from "@mantine/core"; import {
import AppItem from "../../homepage/components/AppItem"; ActionIcon,
Button,
Card,
ColorPicker,
ColorSwatch,
Divider,
Group,
Modal,
SimpleGrid,
Stack,
Text,
} from "@mantine/core";
import { FiEdit } from "react-icons/fi"; import { FiEdit } from "react-icons/fi";
import { useDisclosure, useLocalStorage } from "@mantine/hooks"; import { useDisclosure, useLocalStorage } from "@mantine/hooks";
import '@mantine/core/styles/ModalBase.css'; import "@mantine/core/styles/ModalBase.css";
import '@mantine/core/styles/Modal.css'; import "@mantine/core/styles/Modal.css";
function ThemesSettings() { import { actionIconClasses, modalClasses, textClasses } from "../../../styles";
import ThemeModalContent from "./ThemeModalContent";
function ThemesSettings({ theme }: any) {
const [opened, { open, close }] = useDisclosure(false); const [opened, { open, close }] = useDisclosure(false);
const [value, setValue] = useLocalStorage<any>({ key: 'theme', defaultValue: {} }); const [pickerOpen, { open: openPicker, close: closePicker }] = useDisclosure(false);
const [colorValue, onChange] = useState('#000000');
const [value, setValue] = useLocalStorage<any>({
key: "theme",
defaultValue: {},
});
const { data, isFetched } = useQueryAPI({ const { data, isFetched } = useQueryAPI({
endpoint: "api/themes", endpoint: "api/themes",
}); });
@@ -23,32 +41,83 @@ function ThemesSettings() {
const handleOpen = (theme: any) => { const handleOpen = (theme: any) => {
setSelectedTheme(theme); setSelectedTheme(theme);
open(); open();
} };
const handleClose = () => {
close();
closePicker();
};
return ( return (
<> <>
<Modal.Root opened={opened} onClose={close}> <Modal.Root
size={"50%"}
style={{
"--color-text-pri": theme.colortextpri,
"--color-text-acc": theme.colortextacc,
"--color-background": theme.colorbackground,
"--color-border": theme.colorborder,
"--color-search-background": theme.colorsearchbackground,
}}
className={modalClasses.editThemeModal}
classNames={modalClasses}
opened={opened}
onClose={handleClose}
>
<Modal.Overlay /> <Modal.Overlay />
<Modal.Content> <Modal.Content>
<Modal.Header> <Modal.Header>
<Modal.Title>Edit Theme</Modal.Title> <Modal.Title>Edit Theme</Modal.Title>
<Modal.CloseButton /> <Modal.CloseButton />
</Modal.Header> </Modal.Header>
<Modal.Body> <Modal.Body p={"2rem"}>
<Stack style={{ backgroundColor: selectedTheme.colorbackground, borderRadius: '10px' }}> <ThemeModalContent selectedTheme={selectedTheme} />
<Group justify="center" style={{ color: selectedTheme.colortextpri }}>{selectedTheme.name}</Group> <Divider color="black" my="sm" />
<AppItem <Group w={"100%"} h={"100%"} gap={0}>
name={'Example App'} <Stack w={"50%"}>
url={'https://example.com'} <Group c={'black'} justify="space-between">
icon={'mdi:apps'} <Text>Primary Color</Text>
category={'Default'} <ColorSwatch onClick={openPicker} radius={"sm"} w={"100px"} color={selectedTheme.colortextpri} />
/> </Group>
<Group c={'black'} justify="space-between">
<Text>Accent Color</Text>
<ColorSwatch radius={"sm"} w={"100px"} color={selectedTheme.colortextacc} />
</Group>
<Group c={'black'} justify="space-between">
<Text>Background Color</Text>
<ColorSwatch radius={"sm"} w={"100px"} color={selectedTheme.colorbackground} />
</Group>
<Group c={'black'} justify="space-between">
<Text>Border Color</Text>
<ColorSwatch radius={"sm"} w={"100px"} color={selectedTheme.colorborder} />
</Group>
<Group c={'black'} justify="space-between">
<Text>Search Background Color</Text>
<ColorSwatch radius={"sm"} w={"100px"} color={selectedTheme.colorsearchbackground} />
</Group>
</Stack> </Stack>
<Stack h={"100%"} w={'50%'} align="center" justify="center">
{pickerOpen ? (
<>
<ColorPicker value={colorValue} onChange={onChange}/>
<Group><Text c={'black'}>{colorValue}</Text><Button onClick={() => {setSelectedTheme({ ...selectedTheme, colorbackground: colorValue }); closePicker();}} c={'white'} bg={'forestgreen'}>Save</Button></Group>
</>
) : (
<Card bg={'#0002'} h={"100%"} w={"90%"}>
<Text c="black">
Pick a swatch to edit the color.
</Text>
</Card>
)}
</Stack>
</Group>
</Modal.Body> </Modal.Body>
</Modal.Content> </Modal.Content>
</Modal.Root> </Modal.Root>
<Stack h={'100%'} w={'100%'}> <Stack h={"100%"} w={"100%"}>
<h2>Themes</h2> <Text className={textClasses.settingsSubTitle} classNames={textClasses}>
Themes
</Text>
<SimpleGrid cols={5}> <SimpleGrid cols={5}>
{themesList.map((theme: any) => ( {themesList.map((theme: any) => (
<Stack key={theme.name}> <Stack key={theme.name}>
@@ -60,7 +129,7 @@ function ThemesSettings() {
}} }}
data-theme={theme.name} data-theme={theme.name}
onClick={() => { onClick={() => {
setValue(theme.name) setValue(theme.name);
}} }}
> >
{theme.name} {theme.name}
@@ -74,8 +143,12 @@ function ThemesSettings() {
<ColorSwatch color={theme.colorborder} /> <ColorSwatch color={theme.colorborder} />
</Group> </Group>
<ActionIcon <ActionIcon
className={actionIconClasses.editButton}
classNames={actionIconClasses}
onClick={() => handleOpen(theme)} onClick={() => handleOpen(theme)}
><FiEdit/></ActionIcon> >
<FiEdit />
</ActionIcon>
</Group> </Group>
</Stack> </Stack>
))} ))}

View File

@@ -1,12 +1,37 @@
.backButton { .backButton {
&.root { &.root {
background-color: transparent; background-color: transparent !important;
color: black; color: var(--color-text-pri) !important;
width: 2rem; width: 2rem;
height: 2rem; height: 2rem;
margin-right: 1rem;
.icon { .icon {
font-size: 24px; font-size: 24px;
} }
.loader {
display: none;
}
:hover {
background-color: transparent !important;
color: var(--color-text-pri) !important;
}
} }
} }
.editButton {
&.root {
background-color: transparent !important;
color: var(--color-text-pri) !important;
.icon {
font-size: 24px;
}
.loader {
display: none;
}
:hover {
background-color: var(--color-text-pri) !important;
color: var(--color-text-acc) !important;
}
}
}

View File

@@ -0,0 +1,27 @@
.editThemeModal {
.header {
color: white;
background-color: var(--color-background);
}
.content {
justify-content: center;
align-content: center;
color: light-dark(var(--mantine-primary-color-6), white);
background-color: white;
}
.body {
display: flex;
flex-direction: column;
align-items: center;
}
.close {
color: var(--mantine-color-appRed-3);
background-color: var(--mantine-color-appRed-1);
max-width: 20px!important;
max-height: 20px!important;
&:hover {
color: var(--mantine-color-appRed-1);
background-color: var(--mantine-color-appRed-3);
}
}
}

View File

@@ -3,4 +3,11 @@
padding-left: 2rem; padding-left: 2rem;
height: 100%; height: 100%;
} }
.tab {
color: var(--color-text-pri);
&:hover {
color: var(--color-background);
background-color: var(--color-text-acc);
}
}
} }

View File

@@ -86,6 +86,26 @@
} }
} }
.settingsTitle {
&.root {
font-size: 32px;
font-weight: bold;
padding-top: 1.5rem;
padding-bottom: 1.5rem;
color: var(--color-text-pri);
}
}
.settingsSubTitle {
&.root {
font-size: 24px;
font-weight: bold;
padding-top: 1.5rem;
padding-bottom: 1.5rem;
color: var(--color-text-pri);
}
}
.settingsLink { .settingsLink {
&.root { &.root {
font-size: 1.25em; font-size: 1.25em;

View File

@@ -1,12 +1,25 @@
import actionIconClasses from './ActionIcon.module.css'; import actionIconClasses from "./ActionIcon.module.css";
import buttonClasses from './Button.module.css'; import buttonClasses from "./Button.module.css";
import menuClasses from './Menu.module.css'; import menuClasses from "./Menu.module.css";
import navLinkClasses from './NavLink.module.css'; import modalClasses from "./Modal.module.css";
import popoverClasses from './Popover.module.css'; import navLinkClasses from "./NavLink.module.css";
import selectClasses from './Select.module.css'; import popoverClasses from "./Popover.module.css";
import stackClasses from './Stack.module.css'; import selectClasses from "./Select.module.css";
import tabClasses from './Tabs.module.css'; import stackClasses from "./Stack.module.css";
import textClasses from './Text.module.css'; import tabClasses from "./Tabs.module.css";
import textInputClasses from './TextInput.module.css'; import textClasses from "./Text.module.css";
import textInputClasses from "./TextInput.module.css";
export { actionIconClasses, buttonClasses, menuClasses, navLinkClasses, popoverClasses, selectClasses, stackClasses, tabClasses, textClasses, textInputClasses } export {
actionIconClasses,
buttonClasses,
menuClasses,
modalClasses,
navLinkClasses,
popoverClasses,
selectClasses,
stackClasses,
tabClasses,
textClasses,
textInputClasses,
};

View File

@@ -1,15 +1,15 @@
import react from '@vitejs/plugin-react'; import react from "@vitejs/plugin-react";
import { defineConfig } from 'vite'; import { defineConfig } from "vite";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
server: { server: {
port: 3000, port: 3000,
host: '0.0.0.0', host: "0.0.0.0",
proxy: { proxy: {
'/api': { "/api": {
target: 'http://localhost:5000/trigger', target: "http://localhost:5000/trigger",
changeOrigin: false, changeOrigin: false,
secure: false, secure: false,
}, },
@@ -17,10 +17,10 @@ export default defineConfig({
}, },
preview: { preview: {
port: 3000, port: 3000,
host: '0.0.0.0', host: "0.0.0.0",
proxy: { proxy: {
api: { api: {
target: 'http://localhost:5000', target: "http://localhost:5000",
changeOrigin: false, changeOrigin: false,
secure: false, secure: false,
}, },