Styles, API calls, and more
This commit is contained in:
1375
package-lock.json
generated
1375
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
48
package.json
48
package.json
@@ -10,26 +10,36 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/react": "^4.1.1",
|
||||
"@mantine/core": "^7.9.1",
|
||||
"@mantine/hooks": "^7.9.1",
|
||||
"@tanstack/react-query": "^5.36.0",
|
||||
"jotai": "^2.8.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^5.2.1",
|
||||
"react-query": "^3.39.3"
|
||||
"@emotion/react": "^11.13.0",
|
||||
"@iconify/react": "^5.0.2",
|
||||
"@mantine/core": "^7.12.1",
|
||||
"@mantine/dates": "^7.12.1",
|
||||
"@mantine/form": "^7.12.1",
|
||||
"@mantine/hooks": "^7.12.1",
|
||||
"@mantine/modals": "^7.12.1",
|
||||
"@tabler/icons-react": "^3.12.0",
|
||||
"@tanstack/react-query": "^5.51.23",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.12",
|
||||
"jotai": "^2.9.3",
|
||||
"mantine-react-table": "^2.0.0-beta.6",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-icons": "^5.3.0",
|
||||
"react-query": "^3.39.3",
|
||||
"react-router-dom": "^6.26.0",
|
||||
"react-toastify": "^10.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.66",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
||||
"@typescript-eslint/parser": "^7.2.0",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.6",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.2.0"
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.1.0",
|
||||
"@typescript-eslint/parser": "^8.1.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"eslint": "^9.9.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"eslint-plugin-react-refresh": "^0.4.9",
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^5.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#root {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.logo {
|
||||
|
||||
23
src/App.tsx
23
src/App.tsx
@@ -2,6 +2,14 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import './App.css'
|
||||
import Homepage from './pages/homepage/Homepage'
|
||||
import { MantineProvider } from '@mantine/core'
|
||||
import {
|
||||
Route,
|
||||
createBrowserRouter,
|
||||
createRoutesFromElements,
|
||||
RouterProvider,
|
||||
Navigate,
|
||||
} from "react-router-dom";
|
||||
import Settings from './pages/settings/Settings';
|
||||
|
||||
function App() {
|
||||
const queryClient = new QueryClient({
|
||||
@@ -13,10 +21,21 @@ function App() {
|
||||
},
|
||||
});
|
||||
|
||||
const router = createBrowserRouter(
|
||||
createRoutesFromElements(
|
||||
<Route>
|
||||
<Route path="/trigger" element={<Homepage />}></Route>
|
||||
<Route path="/trigger/settings" element={<Settings />}></Route>
|
||||
</Route>,
|
||||
),
|
||||
);
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MantineProvider>
|
||||
<Homepage />
|
||||
<MantineProvider theme={{
|
||||
fontFamily: '-apple-system,Jost,BlinkMacSystemFont,Helvetica Neue,Roboto,sans-serif',
|
||||
}}>
|
||||
<RouterProvider router={router} />
|
||||
</MantineProvider>
|
||||
</QueryClientProvider>
|
||||
)
|
||||
|
||||
99
src/DataTable/DataTable.tsx
Normal file
99
src/DataTable/DataTable.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import { ModalsProvider } from "@mantine/modals";
|
||||
import { MantineReactTable, useMantineReactTable } from "mantine-react-table"
|
||||
import { memo } from "react";
|
||||
// import '../dataTableStyles.css';
|
||||
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
|
||||
|
||||
export function DataTableComponent({
|
||||
columns,
|
||||
tableData,
|
||||
isFetching,
|
||||
isLoading,
|
||||
onCreatingRowSave,
|
||||
onEditingRowSave,
|
||||
}: any) {
|
||||
|
||||
const table = useMantineReactTable({
|
||||
columns: columns,
|
||||
data: tableData,
|
||||
enableFacetedValues: true,
|
||||
enablePagination: false,
|
||||
enableRowVirtualization: true,
|
||||
enableEditing: true,
|
||||
createDisplayMode: "modal",
|
||||
editDisplayMode: "row",
|
||||
enableBottomToolbar: false,
|
||||
enableToolbarInternalActions: false,
|
||||
onCreatingRowSave: onCreatingRowSave,
|
||||
onEditingRowSave: onEditingRowSave,
|
||||
getRowId: (row: any) => row.id,
|
||||
initialState: {
|
||||
showProgressBars: isFetching,
|
||||
showLoadingOverlay: isLoading,
|
||||
isLoading: isLoading,
|
||||
},
|
||||
state: {
|
||||
showProgressBars: isFetching,
|
||||
showLoadingOverlay: isLoading,
|
||||
isLoading: isLoading,
|
||||
},
|
||||
// mantineToolbarAlertBannerBadgeProps: {
|
||||
// style: {
|
||||
// color: "red",
|
||||
// },
|
||||
// },
|
||||
// mantineTopToolbarProps: {
|
||||
// style: {
|
||||
// color: "var(--color-text-pri)",
|
||||
// background: "var(--color-background)",
|
||||
// border: "none",
|
||||
// boxShadow: "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 (
|
||||
<ModalsProvider>
|
||||
<MantineReactTable table={table} />
|
||||
</ModalsProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export const DataTable = memo(DataTableComponent);
|
||||
|
||||
export default DataTable;
|
||||
117
src/DataTable/columns/ApplicationColumns.tsx
Normal file
117
src/DataTable/columns/ApplicationColumns.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import { MRT_ColumnDef } from "mantine-react-table";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export default function ApplicationColumns() {
|
||||
const columns = useMemo<MRT_ColumnDef<any>[]>(
|
||||
() => [
|
||||
{
|
||||
accessorFn: (row) => {
|
||||
if (row.id) {
|
||||
return `${row.id}`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
enableEditing: false,
|
||||
id: "id",
|
||||
header: "ID",
|
||||
size: 50,
|
||||
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||
className: "id",
|
||||
id: `${cell.getValue()}-id`,
|
||||
}),
|
||||
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||
Header: ({ column }) => column.columnDef.header,
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => {
|
||||
if (row.name) {
|
||||
return `${row.name}`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
id: "name",
|
||||
header: "Name",
|
||||
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||
className: "name",
|
||||
id: `${cell.getValue()}-name`,
|
||||
}),
|
||||
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||
Header: ({ column }) => column.columnDef.header,
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => {
|
||||
if (row.url) {
|
||||
return `${row.url}`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
id: "url",
|
||||
header: "Url",
|
||||
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||
className: "url",
|
||||
id: `${cell.getValue()}-url`,
|
||||
}),
|
||||
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||
Header: ({ column }) => column.columnDef.header,
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => {
|
||||
if (row.icon) {
|
||||
return `${row.icon}`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
id: "icon",
|
||||
header: "Icon",
|
||||
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||
className: "icon",
|
||||
id: `${cell.getValue()}-icon`,
|
||||
}),
|
||||
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||
Header: ({ column }) => column.columnDef.header,
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => {
|
||||
if (row.urlbase) {
|
||||
return `${row.urlbase}`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
id: "urlbase",
|
||||
header: "Base Url",
|
||||
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||
className: "urlbase",
|
||||
id: `${cell.getValue()}-urlbase`,
|
||||
}),
|
||||
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||
Header: ({ column }) => column.columnDef.header,
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => {
|
||||
if (row.category) {
|
||||
return `${row.category}`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
id: "category",
|
||||
header: "Category",
|
||||
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||
className: "category",
|
||||
id: `${cell.getValue()}-category`,
|
||||
}),
|
||||
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||
Header: ({ column }) => column.columnDef.header,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
87
src/DataTable/columns/BookmarkColumns.tsx
Normal file
87
src/DataTable/columns/BookmarkColumns.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import { MRT_ColumnDef } from "mantine-react-table";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export default function ApplicationColumns(groups: any, groupData: any) {
|
||||
const columns = useMemo<MRT_ColumnDef<any>[]>(
|
||||
() => [
|
||||
{
|
||||
accessorFn: (row) => {
|
||||
if (row.id) {
|
||||
return `${row.id}`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
enableEditing: false,
|
||||
id: "id",
|
||||
header: "ID",
|
||||
size: 50,
|
||||
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||
className: "id",
|
||||
id: `${cell.getValue()}-id`,
|
||||
}),
|
||||
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||
Header: ({ column }) => column.columnDef.header,
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => {
|
||||
if (row.name) {
|
||||
return `${row.name}`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
id: "name",
|
||||
header: "Name",
|
||||
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||
className: "name",
|
||||
id: `${cell.getValue()}-name`,
|
||||
}),
|
||||
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||
Header: ({ column }) => column.columnDef.header,
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => {
|
||||
if (row.url) {
|
||||
return `${row.url}`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
id: "url",
|
||||
header: "Url",
|
||||
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||
className: "url",
|
||||
id: `${cell.getValue()}-url`,
|
||||
}),
|
||||
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||
Header: ({ column }) => column.columnDef.header,
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => {
|
||||
const index: number = row.category;
|
||||
if (index !== null && index !== undefined) {
|
||||
return `${index}`;
|
||||
} else {
|
||||
return "N/A";
|
||||
}
|
||||
},
|
||||
id: "category",
|
||||
header: "Category",
|
||||
editVariant: "select",
|
||||
mantineEditSelectProps: { data: groups },
|
||||
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||
className: "category",
|
||||
id: `${cell.getValue()}-category`,
|
||||
}),
|
||||
Cell: ({ renderedCellValue, row }) =>
|
||||
groupData[row?.original.category].category_name,
|
||||
Header: ({ column }) => column.columnDef.header,
|
||||
},
|
||||
],
|
||||
[groups],
|
||||
);
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
66
src/DataTable/columns/MessagesColumns.tsx
Normal file
66
src/DataTable/columns/MessagesColumns.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { MRT_ColumnDef } from "mantine-react-table";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export default function MessagesColumns() {
|
||||
const columns = useMemo<MRT_ColumnDef<any>[]>(
|
||||
() => [
|
||||
{
|
||||
accessorFn: (row) => {
|
||||
if (row.id) {
|
||||
return `${row.id}`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
enableEditing: false,
|
||||
id: "id",
|
||||
header: "ID",
|
||||
size: 50,
|
||||
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||
className: "id",
|
||||
id: `${cell.getValue()}-id`,
|
||||
}),
|
||||
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||
Header: ({ column }) => column.columnDef.header,
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => {
|
||||
if (row.message) {
|
||||
return `${row.message}`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
id: "message",
|
||||
header: "Message",
|
||||
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||
className: "message",
|
||||
id: `${cell.getValue()}-message`,
|
||||
}),
|
||||
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||
Header: ({ column }) => column.columnDef.header,
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => {
|
||||
if (row.easteregg) {
|
||||
return `${row.easteregg}`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
id: "easteregg",
|
||||
header: "Easter Egg?",
|
||||
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||
className: "easteregg",
|
||||
id: `${cell.getValue()}-easteregg`,
|
||||
}),
|
||||
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||
Header: ({ column }) => column.columnDef.header,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
65
src/DataTable/columns/PersonalizationColumns.tsx
Normal file
65
src/DataTable/columns/PersonalizationColumns.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { MRT_ColumnDef } from "mantine-react-table";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export default function PersonalizationColumns() {
|
||||
const columns = useMemo<MRT_ColumnDef<any>[]>(
|
||||
() => [
|
||||
{
|
||||
accessorFn: (row: any) => {
|
||||
if (row.firstname) {
|
||||
return `${row.firstname}`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
id: "firstname",
|
||||
header: "First Name",
|
||||
size: 50,
|
||||
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||
className: "firstname",
|
||||
id: `${cell.getValue()}-firstname`,
|
||||
}),
|
||||
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||
Header: ({ column }) => column.columnDef.header,
|
||||
},
|
||||
{
|
||||
accessorFn: (row: any) => {
|
||||
if (row.lastname) {
|
||||
return `${row.lastname}`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
id: "lastname",
|
||||
header: "Last Name",
|
||||
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||
className: "lastname",
|
||||
id: `${cell.getValue()}-lastname`,
|
||||
}),
|
||||
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||
Header: ({ column }) => column.columnDef.header,
|
||||
},
|
||||
{
|
||||
accessorFn: (row: any) => {
|
||||
if (row.email) {
|
||||
return `${row.email}`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
id: "email",
|
||||
header: "Email",
|
||||
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||
className: "email",
|
||||
id: `${cell.getValue()}-email`,
|
||||
}),
|
||||
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||
Header: ({ column }) => column.columnDef.header,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
16
src/components/ToastError.tsx
Normal file
16
src/components/ToastError.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Stack, Text } from '@mantine/core';
|
||||
import { stackClasses } from '../styles';
|
||||
|
||||
export function ToastError({ err }: { err: any }) {
|
||||
const errorResponse = JSON.parse(err.message);
|
||||
return (
|
||||
<Stack className={stackClasses.VStack} style={{ alignItems: 'flex-start' }}>
|
||||
<Text>
|
||||
Status: {errorResponse.status} {errorResponse.statusText}
|
||||
</Text>
|
||||
<Text>Details: {errorResponse.msg}</Text>
|
||||
{errorResponse.loc && <Text>Location: {errorResponse.loc}</Text>}
|
||||
{errorResponse.type && <Text>Type: {errorResponse.type}</Text>}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
198
src/dataTableStyles.css
Normal file
198
src/dataTableStyles.css
Normal file
@@ -0,0 +1,198 @@
|
||||
.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 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mrt-table-head-sort-button[data-sorted='asc'] {
|
||||
display: flex;
|
||||
min-height: 12px !important;
|
||||
min-width: 12px !important;
|
||||
max-width: 16px;
|
||||
max-height: 16px;
|
||||
|
||||
& > * > .tabler-icon-arrows-sort {
|
||||
height: 12px !important;
|
||||
width: 12px !important;
|
||||
color: light-dark(--mantine-primary-color-6, --mantine-color-appGray-5);
|
||||
}
|
||||
}
|
||||
.mrt-table-head-sort-button[data-sorted='desc'] {
|
||||
display: flex;
|
||||
min-height: 12px !important;
|
||||
min-width: 12px !important;
|
||||
max-width: 16px;
|
||||
max-height: 16px;
|
||||
|
||||
& > * > .tabler-icon-arrows-sort {
|
||||
height: 12px !important;
|
||||
width: 12px !important;
|
||||
color: light-dark(--mantine-primary-color-6, --mantine-color-appGray-5);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useQuery, useQueryClient, useMutation, useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { useCallback } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export function useQueryAPI({ endpoint }: { endpoint: string }) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const callEndpoint = async (endpoint: string) => {
|
||||
const url = `http://localhost:5000/trigger/${endpoint}`;
|
||||
const options: object = {
|
||||
@@ -16,22 +18,425 @@ export function useQueryAPI({ endpoint }: { endpoint: string }) {
|
||||
'Sec-Fetch-Site': 'same-site',
|
||||
},
|
||||
};
|
||||
|
||||
const response = await fetch(url, options);
|
||||
if (!response.ok) {
|
||||
throw new Error(`${response.status} ${response.statusText}`);
|
||||
}
|
||||
const responseData = await response.json();
|
||||
return responseData;
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['GET', endpoint],
|
||||
queryFn: () => callEndpoint(endpoint),
|
||||
retry: false,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
staleTime: 30 * (60 * 1000), // 30 minutes
|
||||
// cacheTime: 60 * (60 * 1000), // 60 minutes
|
||||
});
|
||||
}
|
||||
|
||||
export function useMutationAPI() {
|
||||
|
||||
const callEndpoint = async (
|
||||
method: string,
|
||||
endpoint: string,
|
||||
body: object | undefined | null,
|
||||
) => {
|
||||
|
||||
const url = `http://localhost:5000/trigger/${endpoint}`;
|
||||
const options: any = {
|
||||
method: method,
|
||||
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'
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
};
|
||||
|
||||
const response = await fetch(url, options);
|
||||
if (!response.ok) {
|
||||
const responseData = await response.json();
|
||||
if (typeof responseData.detail !== 'string') {
|
||||
const errorResponse = JSON.stringify({
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
method: options.method,
|
||||
endpoint: endpoint,
|
||||
msg: responseData.detail[0]?.msg,
|
||||
loc: responseData.detail[0]?.loc,
|
||||
type: responseData.detail[0]?.type,
|
||||
});
|
||||
|
||||
throw new Error(errorResponse);
|
||||
}
|
||||
if (typeof responseData.detail === 'object') {
|
||||
const errorResponse = JSON.stringify({
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
method: options.method,
|
||||
endpoint: endpoint,
|
||||
msg: responseData.detail.message,
|
||||
});
|
||||
|
||||
throw new Error(errorResponse);
|
||||
}
|
||||
const errorResponse = JSON.stringify({
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
method: options.method,
|
||||
endpoint: endpoint,
|
||||
msg: responseData.detail,
|
||||
});
|
||||
|
||||
throw new Error(errorResponse);
|
||||
}
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
method,
|
||||
endpoint,
|
||||
body = undefined,
|
||||
}: {
|
||||
method: string;
|
||||
endpoint: string;
|
||||
body?: object | undefined | null;
|
||||
}) => callEndpoint(method, endpoint, body),
|
||||
onMutate: () => {},
|
||||
onSuccess: (data, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['GET', variables.endpoint] });
|
||||
|
||||
if (variables.endpoint.includes('/')) {
|
||||
if ((variables.endpoint.match(/\//g) || []).length > 2) {
|
||||
const GetEndpoint = variables.endpoint.split('/');
|
||||
const Endpoint = `${GetEndpoint[1]}/${GetEndpoint[2]}`;
|
||||
queryClient.invalidateQueries({ queryKey: ['GET', `/${Endpoint}`] });
|
||||
} else {
|
||||
const GetEndpoint = variables.endpoint.split('/')[1];
|
||||
queryClient.invalidateQueries({ queryKey: ['GET', `/${GetEndpoint}`] });
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useFileUploadAPI() {
|
||||
|
||||
const callEndpoint = async (
|
||||
method: string,
|
||||
endpoint: string,
|
||||
body?: object | FormData | undefined | null,
|
||||
) => {
|
||||
|
||||
const url = `http://localhost:5000/trigger/${endpoint}`;
|
||||
|
||||
const options = {
|
||||
method: method,
|
||||
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',
|
||||
},
|
||||
body: body ?? undefined,
|
||||
};
|
||||
const response = await fetch(url, options as unknown as Body);
|
||||
if (!response.ok) {
|
||||
const responseData = await response.json();
|
||||
if (typeof responseData.detail !== 'string') {
|
||||
const errorResponse = JSON.stringify({
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
method: options.method,
|
||||
endpoint: endpoint,
|
||||
msg: responseData.detail[0].msg,
|
||||
loc: responseData.detail[0].loc,
|
||||
type: responseData.detail[0].type,
|
||||
});
|
||||
|
||||
throw new Error(errorResponse);
|
||||
}
|
||||
const errorResponse = JSON.stringify({
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
method: options.method,
|
||||
endpoint: endpoint,
|
||||
msg: responseData.detail,
|
||||
});
|
||||
|
||||
throw new Error(errorResponse);
|
||||
}
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
method,
|
||||
endpoint,
|
||||
body,
|
||||
}: {
|
||||
method: string;
|
||||
endpoint: string;
|
||||
body: object | FormData | undefined | null;
|
||||
toastID?: string;
|
||||
}) => callEndpoint(method, endpoint, body),
|
||||
onMutate: () => {},
|
||||
onSuccess: (variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['GET', variables.endpoint] });
|
||||
if (variables.endpoint?.includes('/')) {
|
||||
const GetEndpoint = variables.endpoint.split('/')[1];
|
||||
queryClient.invalidateQueries({ queryKey: ['GET', `/${GetEndpoint}`] });
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useFetchAllRecordsAPI({ endpoint }: { endpoint: string }) {
|
||||
const pageSize = 500;
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const fetchPage = async (page: number, retryCount = 0): Promise<any> => {
|
||||
|
||||
// Start building the URL
|
||||
const url = new URL(`http://localhost:5000/trigger/${endpoint}`);
|
||||
|
||||
// Check and append custom query parameters from the endpoint
|
||||
const customParams = new URLSearchParams(endpoint.split('?')[1]);
|
||||
for (const [key, value] of customParams) {
|
||||
if (!url.searchParams.has(key)) {
|
||||
url.searchParams.append(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Add 'page' and 'size' parameters
|
||||
url.searchParams.set('page', page.toString()); // Use 'set' to ensure uniqueness
|
||||
url.searchParams.set('size', pageSize.toString());
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(url.toString(), options);
|
||||
|
||||
if (!response.ok) {
|
||||
// If the response is not ok and status is 401 (not authorized), reload the page
|
||||
if (response.status === 401) {
|
||||
window.location.reload();
|
||||
}
|
||||
throw new Error(
|
||||
`Error fetching page ${page}: ${response.status} ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
return response.json();
|
||||
} catch (err) {
|
||||
if (retryCount < 3) {
|
||||
// Retry up to 3 times with a random delay
|
||||
const delay = Math.floor(Math.random() * 3000) + 1000; // Random delay between 1 to 3 seconds
|
||||
console.error(
|
||||
`Retrying page ${page}, attempt ${retryCount + 1} after ${delay} ms`,
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
return fetchPage(page, retryCount + 1);
|
||||
}
|
||||
console.error(`Error fetching page ${page} after 3 retries:`, err);
|
||||
throw err; // Re-throw the error to handle it in the calling function
|
||||
}
|
||||
};
|
||||
|
||||
const callEndpoint = async () => {
|
||||
try {
|
||||
// Fetch the first page to get the total number of items and pages
|
||||
const firstPageData = await fetchPage(1);
|
||||
// If the first page doesn't have a 'pages' property, return the data as-is
|
||||
if (firstPageData?.pages === undefined) {
|
||||
return firstPageData;
|
||||
}
|
||||
if (firstPageData?.pages === 1) {
|
||||
return firstPageData.items;
|
||||
}
|
||||
|
||||
let totalPages = firstPageData.pages;
|
||||
const allItems = [...firstPageData.items];
|
||||
// Fetch each page sequentially
|
||||
for (let page = 2; page <= totalPages; page++) {
|
||||
try {
|
||||
const pageData = await fetchPage(page);
|
||||
allItems.push(...pageData.items);
|
||||
|
||||
// Check and update the totalPages if it has increased
|
||||
if (pageData.pages > totalPages) {
|
||||
totalPages = pageData.pages;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Final error fetching page ${page}:`, err);
|
||||
// Continue to the next page even if the current page fails
|
||||
}
|
||||
}
|
||||
|
||||
return allItems;
|
||||
} catch (error) {
|
||||
// Handling errors in fetching the first page
|
||||
console.error('Error fetching the first page:', error);
|
||||
return []; // Return empty array on error
|
||||
}
|
||||
};
|
||||
|
||||
const invalidate = useCallback(() => {
|
||||
queryClient.invalidateQueries({ queryKey: ['GET', endpoint] });
|
||||
}, [queryClient, endpoint]);
|
||||
|
||||
return {
|
||||
...useQuery({
|
||||
queryKey: ['GET', endpoint],
|
||||
queryFn: callEndpoint,
|
||||
}),
|
||||
invalidate,
|
||||
};
|
||||
}
|
||||
|
||||
interface DownloadFileOptions {
|
||||
endpoint: string;
|
||||
file_name: string;
|
||||
}
|
||||
|
||||
export function useDownloadFile() {
|
||||
const downloadFile = useCallback(
|
||||
async ({ endpoint, file_name }: DownloadFileOptions) => {
|
||||
const url = `http://localhost:5000/trigger/${endpoint}`;
|
||||
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 url = `http://localhost:5000/trigger/${endpoint}`;
|
||||
const requestOptions: 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'
|
||||
},
|
||||
};
|
||||
try {
|
||||
const response = await fetch(url, requestOptions);
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error fetching transactions:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
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 };
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Jost:ital,wght@0,100;0,200;0,400;1,100;1,200&display=swap');
|
||||
|
||||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
@@ -54,6 +56,14 @@ button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
#settings-container {
|
||||
justify-content: left;
|
||||
}
|
||||
|
||||
#settings-nav {
|
||||
width: 25vw;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Stack } from '@mantine/core';
|
||||
import { useEffect, useState } from 'react';
|
||||
import stackClasses from '../../styles/Stack.module.css';
|
||||
import {useQueryAPI} from '../../hooks/useAPI';
|
||||
import Search from './components/Search';
|
||||
@@ -9,6 +8,7 @@ import Bookmarks from './components/Bookmarks';
|
||||
import SettingsButton from './components/SettingsButton';
|
||||
import { useAtom } from 'jotai';
|
||||
import { HomepageFilters } from '../../contexts/HomepageFilters';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
|
||||
export default function Homepage() {
|
||||
|
||||
@@ -35,9 +35,10 @@ function AppItem({ url, icon, name }: any) {
|
||||
gap: 0,
|
||||
}}
|
||||
>
|
||||
<Text className={texts.appName}>{name}</Text>
|
||||
<Text className={texts.appName} classNames={texts}>{name}</Text>
|
||||
<Text
|
||||
className={texts.appUrl}
|
||||
classNames={texts}
|
||||
style={{
|
||||
textDecoration: textDecoration,
|
||||
}}
|
||||
|
||||
@@ -16,10 +16,10 @@ function Applications() {
|
||||
|
||||
return (
|
||||
<Stack id='apps' style={{ gap: 0 }}>
|
||||
<Text className={texts.sectionTitle}>Applications</Text>
|
||||
<Text className={texts.sectionTitle} classNames={texts}>Applications</Text>
|
||||
<Stack
|
||||
style={{
|
||||
// background: 'var(--color-background)',
|
||||
background: 'var(--color-background)',
|
||||
gap: '.25rem',
|
||||
backgroundSize: 'cover',
|
||||
borderBottom: '0px solid',
|
||||
|
||||
@@ -25,7 +25,7 @@ function Links() {
|
||||
<>
|
||||
{groupsIsFetched && linksIsFetched && categories?.length > 0 && (
|
||||
<Stack id='links' style={{ gap: 0 }}>
|
||||
<Text className={texts.sectionTitle}>Bookmarks</Text>
|
||||
<Text className={texts.sectionTitle} classNames={texts}>Bookmarks</Text>
|
||||
<Stack
|
||||
style={{
|
||||
display: 'grid',
|
||||
|
||||
@@ -11,7 +11,7 @@ function LinkGroups({ links, category }: any) {
|
||||
lineHeight: '1.5rem',
|
||||
}}
|
||||
>
|
||||
<Text className={texts.linkCategory}>{category}</Text>
|
||||
<Text className={texts.linkCategory} classNames={texts}>{category}</Text>
|
||||
{links?.map((link: any) => (
|
||||
<LinkItem key={link.name} link={link} />
|
||||
))}
|
||||
|
||||
@@ -3,7 +3,7 @@ import texts from '../../../styles/Text.module.css';
|
||||
|
||||
function LinkItem({ link }: any) {
|
||||
return (
|
||||
<Text className={texts.linkItem} component='a' href={link.url}>
|
||||
<Text className={texts.linkItem} classNames={texts} component='a' href={link.url}>
|
||||
{link.name}
|
||||
</Text>
|
||||
);
|
||||
|
||||
@@ -24,7 +24,9 @@ function SettingsButton() {
|
||||
|
||||
useEffect(() => {
|
||||
if(data) {
|
||||
setCategories(data.map((category: any) => {return {value: category.category, label: category.category}}));
|
||||
let list: [any] = data.map((category: any) => {return {value: category.category, label: category.category}}) || [];
|
||||
list.unshift({value: 'All', label: 'All'});
|
||||
setCategories(list);
|
||||
}
|
||||
}, [data, isFetched]);
|
||||
|
||||
@@ -41,7 +43,9 @@ function SettingsButton() {
|
||||
withArrow
|
||||
shadow='md'
|
||||
position='top'
|
||||
classNames={menu}
|
||||
classNames={{
|
||||
dropdown: menu.dropdown
|
||||
}}
|
||||
styles={{
|
||||
dropdown: {
|
||||
backgroundColor: 'var(--color-background)',
|
||||
@@ -49,7 +53,7 @@ function SettingsButton() {
|
||||
}}
|
||||
>
|
||||
<Menu.Target>
|
||||
<Button className={button.settingsButton}>
|
||||
<Button className={button.settingsButton} classNames={button}>
|
||||
<FiMenu />
|
||||
</Button>
|
||||
</Menu.Target>
|
||||
|
||||
38
src/pages/settings/Settings.tsx
Normal file
38
src/pages/settings/Settings.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { ActionIcon, Group, Stack, Tabs } from "@mantine/core";
|
||||
import tabClasses from '../../styles/Tabs.module.css';
|
||||
import Personalization from "./components/Personalization";
|
||||
import { FiArrowLeftCircle } from "react-icons/fi";
|
||||
import { actionIconClasses } from "../../styles";
|
||||
import Messages from "./components/Messages";
|
||||
import Applications from "./components/Applications";
|
||||
import Bookmarks from "./components/Bookmarks";
|
||||
|
||||
function Settings() {
|
||||
return (
|
||||
<Stack h={'100vh'} px={'2rem'}>
|
||||
<Group gap={0} >
|
||||
<ActionIcon className={actionIconClasses.backButton} classNames={actionIconClasses} component="a" href="/trigger"><FiArrowLeftCircle/></ActionIcon>
|
||||
<h1>Settings</h1>
|
||||
</Group>
|
||||
<Tabs keepMounted orientation="vertical" defaultValue={'personalization'} className={tabClasses.settingsTabs} classNames={tabClasses}>
|
||||
<Tabs.List>
|
||||
<Tabs.Tab value="personalization">Personal Info</Tabs.Tab>
|
||||
<Tabs.Tab value="messages">Messages</Tabs.Tab>
|
||||
<Tabs.Tab value="applications">Applications</Tabs.Tab>
|
||||
<Tabs.Tab value="bookmarksCategories">Bookmark Categories</Tabs.Tab>
|
||||
<Tabs.Tab value="bookmarks">Bookmarks</Tabs.Tab>
|
||||
<Tabs.Tab value="themes">Themes</Tabs.Tab>
|
||||
<Tabs.Tab value="providers">Search Providers</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel value="personalization"><Personalization/></Tabs.Panel>
|
||||
<Tabs.Panel value="messages"><Messages/></Tabs.Panel>
|
||||
<Tabs.Panel value="applications"><Applications/></Tabs.Panel>
|
||||
<Tabs.Panel value="bookmarksCategories">Bookmark Categories</Tabs.Panel>
|
||||
<Tabs.Panel value="bookmarks"><Bookmarks/></Tabs.Panel>
|
||||
<Tabs.Panel value="themes">Themes</Tabs.Panel>
|
||||
<Tabs.Panel value="providers">Search Providers</Tabs.Panel>
|
||||
</Tabs>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
export default Settings;
|
||||
59
src/pages/settings/components/Applications.tsx
Normal file
59
src/pages/settings/components/Applications.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import {useMutationAPI, useQueryAPI} from "../../../hooks/useAPI";
|
||||
import { DataTable } from "../../../DataTable/DataTable";
|
||||
import { toast } from "react-toastify";
|
||||
import { MRT_TableOptions } from "mantine-react-table";
|
||||
import ApplicationColumns from "../../../DataTable/columns/ApplicationColumns";
|
||||
import { Stack } from "@mantine/core";
|
||||
const ApplicationSettings = () => {
|
||||
const endpoint = "api/apps";
|
||||
const { data, isFetching, isLoading, isFetched } = useQueryAPI({
|
||||
endpoint: endpoint,
|
||||
});
|
||||
const [applicationData, setApplicationData] = useState<any>([]);
|
||||
|
||||
const {mutateAsync: createRow, isPending: isCreatingRow} = useMutationAPI();
|
||||
const {mutateAsync: editRow, isPending: isEditingRow} = useMutationAPI();
|
||||
|
||||
useEffect(() => {
|
||||
if(isFetched) {
|
||||
setApplicationData(data);
|
||||
}
|
||||
})
|
||||
|
||||
const handleCreateRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({
|
||||
values,
|
||||
exitCreatingMode,
|
||||
}) => {
|
||||
|
||||
createRow({method: "POST", endpoint: endpoint, body: values}).then(() => {
|
||||
toast.success("Application created successfully");
|
||||
})
|
||||
exitCreatingMode();
|
||||
};
|
||||
const handleEditRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({
|
||||
values,
|
||||
table,
|
||||
}) => {
|
||||
editRow({method: "PATCH", endpoint: endpoint, body: values}).then(() => {
|
||||
toast.success("Application updated successfully");
|
||||
});
|
||||
table.setEditingRow(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<h2>Applications</h2>
|
||||
<DataTable
|
||||
tableName="Applications"
|
||||
columns={ApplicationColumns()}
|
||||
tableData={applicationData}
|
||||
isFetching={isFetching}
|
||||
isLoading={isLoading}
|
||||
onCreatingRowSave={handleCreateRow}
|
||||
onEditingRowSave={handleEditRow}
|
||||
/>
|
||||
</Stack>
|
||||
)
|
||||
};
|
||||
export default ApplicationSettings;
|
||||
76
src/pages/settings/components/Bookmarks.tsx
Normal file
76
src/pages/settings/components/Bookmarks.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import {useMutationAPI, useQueryAPI} from "../../../hooks/useAPI";
|
||||
import { DataTable } from "../../../DataTable/DataTable";
|
||||
import { toast } from "react-toastify";
|
||||
import { MRT_TableOptions } from "mantine-react-table";
|
||||
import { Stack } from "@mantine/core";
|
||||
import BookmarkColumns from "../../../DataTable/columns/BookmarkColumns";
|
||||
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 [bookmarkData, setBookmarkData] = useState<any>([]);
|
||||
const [groups, setGroups] = useState([]);
|
||||
const [fullyFetched, setFullyFetched] = useState(false);
|
||||
const {mutateAsync: createRow, isPending: isCreatingRow} = useMutationAPI();
|
||||
const {mutateAsync: editRow, isPending: isEditingRow} = useMutationAPI();
|
||||
|
||||
useEffect(() => {
|
||||
if(isFetched) {
|
||||
setBookmarkData(data);
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (groupDataisFetched) {
|
||||
let groupList = groupData.map((category: any) => {
|
||||
return category.category_name;
|
||||
});
|
||||
setGroups(groupList);
|
||||
setFullyFetched(true);
|
||||
}
|
||||
}, [groupDataisFetched]);
|
||||
|
||||
const handleCreateRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({
|
||||
values,
|
||||
exitCreatingMode,
|
||||
}) => {
|
||||
|
||||
createRow({method: "POST", endpoint: endpoint, body: values}).then(() => {
|
||||
toast.success("Application created successfully");
|
||||
})
|
||||
exitCreatingMode();
|
||||
};
|
||||
const handleEditRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({
|
||||
values,
|
||||
table,
|
||||
}) => {
|
||||
editRow({method: "PATCH", endpoint: endpoint, body: values}).then(() => {
|
||||
toast.success("Application updated successfully");
|
||||
});
|
||||
table.setEditingRow(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<h2>Bookmarks</h2>
|
||||
<DataTable
|
||||
tableName="Applications"
|
||||
columns={BookmarkColumns(groups, groupData)}
|
||||
tableData={bookmarkData}
|
||||
isFetching={isFetching}
|
||||
isLoading={isLoading}
|
||||
onCreatingRowSave={handleCreateRow}
|
||||
onEditingRowSave={handleEditRow}
|
||||
/>
|
||||
</Stack>
|
||||
)
|
||||
};
|
||||
export default BookmarkSettings;
|
||||
0
src/pages/settings/components/Categories.tsx
Normal file
0
src/pages/settings/components/Categories.tsx
Normal file
59
src/pages/settings/components/Messages.tsx
Normal file
59
src/pages/settings/components/Messages.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import {useMutationAPI, useQueryAPI} from "../../../hooks/useAPI";
|
||||
import { DataTable } from "../../../DataTable/DataTable";
|
||||
import { toast } from "react-toastify";
|
||||
import { MRT_TableOptions } from "mantine-react-table";
|
||||
import MessagesColumns from "../../../DataTable/columns/MessagesColumns";
|
||||
import { Stack } from "@mantine/core";
|
||||
const MessageSettings = () => {
|
||||
const endpoint = "api/messages";
|
||||
const { data, isFetching, isLoading, isFetched } = useQueryAPI({
|
||||
endpoint: endpoint,
|
||||
});
|
||||
const [messageData, setMessageData] = useState<any>([]);
|
||||
|
||||
const {mutateAsync: createRow, isPending: isCreatingRow} = useMutationAPI();
|
||||
const {mutateAsync: editRow, isPending: isEditingRow} = useMutationAPI();
|
||||
|
||||
useEffect(() => {
|
||||
if(isFetched) {
|
||||
setMessageData(data);
|
||||
}
|
||||
})
|
||||
|
||||
const handleCreateRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({
|
||||
values,
|
||||
exitCreatingMode,
|
||||
}) => {
|
||||
|
||||
createRow({method: "POST", endpoint: endpoint, body: values}).then(() => {
|
||||
toast.success("Message created successfully");
|
||||
})
|
||||
exitCreatingMode();
|
||||
};
|
||||
const handleEditRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({
|
||||
values,
|
||||
table,
|
||||
}) => {
|
||||
editRow({method: "PATCH", endpoint: endpoint, body: values}).then(() => {
|
||||
toast.success("Message updated successfully");
|
||||
});
|
||||
table.setEditingRow(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<h2>Messages</h2>
|
||||
<DataTable
|
||||
tableName="Messages"
|
||||
columns={MessagesColumns()}
|
||||
tableData={messageData}
|
||||
isFetching={isFetching}
|
||||
isLoading={isLoading}
|
||||
onCreatingRowSave={handleCreateRow}
|
||||
onEditingRowSave={handleEditRow}
|
||||
/>
|
||||
</Stack>
|
||||
)
|
||||
};
|
||||
export default MessageSettings;
|
||||
58
src/pages/settings/components/Personalization.tsx
Normal file
58
src/pages/settings/components/Personalization.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import {useMutationAPI, useQueryAPI} from "../../../hooks/useAPI";
|
||||
import { DataTable } from "../../../DataTable/DataTable";
|
||||
import PersonalizationColumns from "../../../DataTable/columns/PersonalizationColumns";
|
||||
import { toast } from "react-toastify";
|
||||
import { MRT_TableOptions } from "mantine-react-table";
|
||||
const PersonalizationSettings = () => {
|
||||
const endpoint = "api/personaldata";
|
||||
const { data, isFetching, isLoading, isFetched } = useQueryAPI({
|
||||
endpoint: endpoint,
|
||||
});
|
||||
const [personalData, setPersonalData] = useState<any>([]);
|
||||
|
||||
const {mutateAsync: createRow, isPending: isCreatingRow} = useMutationAPI();
|
||||
const {mutateAsync: editRow, isPending: isEditingRow} = useMutationAPI();
|
||||
|
||||
useEffect(() => {
|
||||
if(isFetched) {
|
||||
setPersonalData(data);
|
||||
}
|
||||
})
|
||||
|
||||
const handleCreateRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({
|
||||
values,
|
||||
exitCreatingMode,
|
||||
}) => {
|
||||
|
||||
createRow({method: "POST", endpoint: endpoint, body: values}).then(() => {
|
||||
toast.success("Personalization created successfully");
|
||||
})
|
||||
exitCreatingMode();
|
||||
};
|
||||
const handleEditRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({
|
||||
values,
|
||||
table,
|
||||
}) => {
|
||||
editRow({method: "PATCH", endpoint: endpoint, body: values}).then(() => {
|
||||
toast.success("Personalization updated successfully");
|
||||
});
|
||||
table.setEditingRow(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>Personalization</h2>
|
||||
<DataTable
|
||||
tableName="Personalization"
|
||||
columns={PersonalizationColumns()}
|
||||
tableData={personalData}
|
||||
isFetching={isFetching}
|
||||
isLoading={isLoading}
|
||||
onCreatingRowSave={handleCreateRow}
|
||||
onEditingRowSave={handleEditRow}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
};
|
||||
export default PersonalizationSettings;
|
||||
0
src/pages/settings/components/Themes.tsx
Normal file
0
src/pages/settings/components/Themes.tsx
Normal file
@@ -1,6 +1,12 @@
|
||||
.root {
|
||||
background-color: transparent;
|
||||
}
|
||||
.icon {
|
||||
color: var(--color-text-acc)
|
||||
.backButton {
|
||||
&.root {
|
||||
background-color: transparent;
|
||||
color: black;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
.icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +1,30 @@
|
||||
.settingsButton {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
color: var(--color-text-pri);
|
||||
background-color: var(--color-background);
|
||||
border-radius: 100%;
|
||||
border: solid 2px var(--color-text-pri);
|
||||
cursor: pointer;
|
||||
&.root {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
color: var(--color-text-pri);
|
||||
background-color: var(--color-background);
|
||||
border-radius: 100%;
|
||||
border: solid 2px var(--color-text-pri);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.themeButton{
|
||||
font-size: 1em;
|
||||
font-weight:500;
|
||||
margin: 10px;
|
||||
width:128px;
|
||||
line-height: 3rem;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
border-radius: 1em;
|
||||
height: 3.5rem;
|
||||
&.root {
|
||||
font-size: 1em;
|
||||
font-weight:500;
|
||||
margin: 10px;
|
||||
width:128px;
|
||||
line-height: 3rem;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
border-radius: 1em;
|
||||
height: 3.5rem;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
.dropdown {
|
||||
background-color: var(--color-background);
|
||||
border: solid 2px var(--color-border);
|
||||
border-color: var(--color-border);
|
||||
.settingsButton {
|
||||
.dropdown {
|
||||
background-color: white;
|
||||
border: solid 2px var(--color-border);
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
}
|
||||
6
src/styles/Tabs.module.css
Normal file
6
src/styles/Tabs.module.css
Normal file
@@ -0,0 +1,6 @@
|
||||
.settingsTabs {
|
||||
.panel {
|
||||
padding-left: 2rem;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
@@ -1,90 +1,106 @@
|
||||
.greeting {
|
||||
margin: 0;
|
||||
margin-left: .5em;
|
||||
margin-right: .5em;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
font-size: 4em;
|
||||
font-weight: 400;
|
||||
width: max-content;
|
||||
color: var(--color-text-acc);
|
||||
}
|
||||
|
||||
.dateTime {
|
||||
display: flex;
|
||||
font-size: 2em;
|
||||
height: 30px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin-right: 1em;
|
||||
text-transform: uppercase;
|
||||
font-weight: 300;
|
||||
color: var(--color-text-pri);
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
text-decoration: underline;
|
||||
font-size: 20px;
|
||||
font-weight: 200;
|
||||
font-style: italic;
|
||||
height: 10px;
|
||||
text-transform: uppercase;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
color: var(--color-text-pri);
|
||||
}
|
||||
|
||||
.appName {
|
||||
font-weight: 400;
|
||||
font-size: 0.8em;
|
||||
font-style: normal;
|
||||
color: light-dark(blue, red);
|
||||
font-family: 'Jost', sans-serif;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.appUrl {
|
||||
font-size: 0.8em;
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-text-acc);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.linkCategory {
|
||||
font-size: 1.1em;
|
||||
font-weight: 400;
|
||||
height: 10px;
|
||||
color: var(--color-text-acc);
|
||||
text-transform: uppercase;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.linkItem {
|
||||
display: block;
|
||||
font-size: 1em;
|
||||
line-height: 2;
|
||||
color: var(--color-text-pri)
|
||||
}
|
||||
|
||||
.settingsLink {
|
||||
font-size: 1.25em;
|
||||
display: block;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
line-height: 2;
|
||||
justify-self: center;
|
||||
color: var(--color-text-pri);
|
||||
border-radius: 1em;
|
||||
&:hover {
|
||||
background-color: var(--color-text-acc);
|
||||
&.root {
|
||||
margin: 0;
|
||||
margin-left: .5em;
|
||||
margin-right: .5em;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
font-size: 4em;
|
||||
font-weight: 400;
|
||||
width: max-content;
|
||||
color: var(--color-text-acc);
|
||||
}
|
||||
}
|
||||
|
||||
.linkItem:hover {
|
||||
text-decoration: underline;
|
||||
.dateTime {
|
||||
&.root {
|
||||
display: flex;
|
||||
font-family: -apple-system, Jost, BlinkMacSystemFont, Helvetica Neue, Roboto, sans-serif !important;
|
||||
font-size: 2em;
|
||||
height: 30px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin-right: 1em;
|
||||
text-transform: uppercase;
|
||||
font-weight: 300;
|
||||
color: var(--color-text-pri);
|
||||
}
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
&.root {
|
||||
text-decoration: underline;
|
||||
font-size: 20px;
|
||||
font-weight: 200;
|
||||
font-style: italic;
|
||||
height: 10px;
|
||||
text-transform: uppercase;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
color: var(--color-text-pri);
|
||||
}
|
||||
}
|
||||
|
||||
.appName {
|
||||
&.root {
|
||||
font-weight: 400;
|
||||
font-size: 0.8em;
|
||||
font-style: normal;
|
||||
color: light-dark(blue, black);
|
||||
font-family: 'Jost', sans-serif;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.appUrl {
|
||||
&.root {
|
||||
font-size: 0.8em;
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-text-acc);
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.linkCategory {
|
||||
&.root {
|
||||
font-size: 1.1em;
|
||||
font-weight: 400;
|
||||
height: 10px;
|
||||
color: var(--color-text-acc);
|
||||
text-transform: uppercase;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.linkItem {
|
||||
&.root {
|
||||
display: block;
|
||||
font-size: 1em;
|
||||
line-height: 2;
|
||||
color: var(--color-text-pri);
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.settingsLink {
|
||||
&.root {
|
||||
font-size: 1.25em;
|
||||
display: block;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
line-height: 2;
|
||||
justify-self: center;
|
||||
color: var(--color-text-pri);
|
||||
border-radius: 1em;
|
||||
&:hover {
|
||||
background-color: var(--color-text-acc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a{
|
||||
|
||||
@@ -1,52 +1,50 @@
|
||||
.keyword{
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
border-bottom: thin solid var(--color-border);
|
||||
color: var(--color-text-pri);
|
||||
font-family: -apple-system, Jost, BlinkMacSystemFont, Helvetica Neue, Roboto, sans-serif;
|
||||
font-style: italic;
|
||||
font-size: 0.8em;
|
||||
height: 3.5em;
|
||||
transition: all 0.4s ease;
|
||||
width: 50%;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
text-align: center;
|
||||
.input {
|
||||
border: 0;
|
||||
border-bottom: thin solid var(--color-border);
|
||||
background-color: transparent;
|
||||
color: var(--color-text-pri);
|
||||
font-style: italic;
|
||||
font-size: 0.8em;
|
||||
height: 3.5em;
|
||||
transition: all 0.4s ease;
|
||||
width: 50%;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
text-align: center;
|
||||
&:focus {
|
||||
background-color: var(--color-search-background);
|
||||
border-color: var(--color-border);
|
||||
outline: none;
|
||||
color: var(--color-text-acc);
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 1.2em;
|
||||
letter-spacing: .2em;
|
||||
width:100%;
|
||||
border-top-left-radius: .25em;
|
||||
border-top-right-radius: .25em;
|
||||
border-bottom-left-radius: .25em;
|
||||
border-bottom-right-radius: .25em;
|
||||
}
|
||||
}
|
||||
.label{
|
||||
color: var(--color-border);
|
||||
display: block;
|
||||
position: relative;
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
font-size: 1.2em;
|
||||
letter-spacing: .2em;
|
||||
opacity: 1;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
top: 28%;
|
||||
}
|
||||
}
|
||||
|
||||
.keyword:focus{
|
||||
background-color: var(--color-search-background);
|
||||
border-color: var(--color-border);
|
||||
outline: none;
|
||||
color: var(--color-text-acc);
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 1.2em;
|
||||
letter-spacing: .2em;
|
||||
width:100%;
|
||||
border-top-left-radius: .25em;
|
||||
border-top-right-radius: .25em;
|
||||
border-bottom-left-radius: .25em;
|
||||
border-bottom-right-radius: .25em;
|
||||
}
|
||||
.keyword::placeholder{
|
||||
text-align: left;
|
||||
}
|
||||
.label{
|
||||
color: var(--color-border);
|
||||
display: block;
|
||||
position: relative;
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
font-size: 1.2em;
|
||||
letter-spacing: .2em;
|
||||
opacity: 1;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
top: 28%;
|
||||
}
|
||||
.keyword:placeholder-shown + .label{
|
||||
visibility: hidden;
|
||||
z-index: 1;
|
||||
|
||||
@@ -5,7 +5,8 @@ import navLinkClasses from './NavLink.module.css';
|
||||
import popoverClasses from './Popover.module.css';
|
||||
import selectClasses from './Select.module.css';
|
||||
import stackClasses from './Stack.module.css';
|
||||
import tabClasses from './Tabs.module.css';
|
||||
import textClasses from './Text.module.css';
|
||||
import textInputClasses from './TextInput.module.css';
|
||||
|
||||
export { actionIconClasses, buttonClasses, menuClasses, navLinkClasses, popoverClasses, selectClasses, stackClasses, textClasses, textInputClasses }
|
||||
export { actionIconClasses, buttonClasses, menuClasses, navLinkClasses, popoverClasses, selectClasses, stackClasses, tabClasses, textClasses, textInputClasses }
|
||||
Reference in New Issue
Block a user