Bridge integration

This commit is contained in:
Darren Clarke 2024-05-09 07:42:44 +02:00
parent 42a5e09c94
commit 162390008b
56 changed files with 776 additions and 591 deletions

View file

@ -1,4 +1,5 @@
export default function Page() {
return <h1>Home</h1>;
}
import { Home } from "bridge-ui";
export default function Page() {
return <Home />;
}

View file

@ -17,7 +17,7 @@ import {
import { signIn } from "next-auth/react";
import Image from "next/image";
import LinkLogo from "@/app/_images/link-logo-small.png";
import { colors } from "ui";
import { colors, fonts } from "ui";
import { useSearchParams } from "next/navigation";
type LoginProps = {
@ -34,6 +34,7 @@ export const Login: FC<LoginProps> = ({ session }) => {
const params = useSearchParams();
const error = params.get("error");
const { darkGray, cdrLinkOrange, white } = colors;
const { poppins } = fonts;
const buttonStyles = {
borderRadius: 500,
width: "100%",
@ -102,7 +103,7 @@ export const Login: FC<LoginProps> = ({ session }) => {
fontWeight: 700,
mt: 1,
ml: 0.5,
fontFamily: "Poppins",
fontFamily: poppins.style.fontFamily,
}}
>
CDR Bridge

View file

@ -1,4 +1,3 @@
FROM node:20 AS base
FROM base AS builder

View file

@ -17,7 +17,7 @@ import {
import { signIn } from "next-auth/react";
import Image from "next/image";
import LinkLogo from "public/link-logo-small.png";
import { colors } from "app/_styles/theme";
import { colors, fonts } from "ui";
import { useSearchParams } from "next/navigation";
type LoginProps = {
@ -34,6 +34,7 @@ export const Login: FC<LoginProps> = ({ session }) => {
const params = useSearchParams();
const error = params.get("error");
const { darkGray, cdrLinkOrange, white } = colors;
const { poppins } = fonts;
const buttonStyles = {
borderRadius: 500,
width: "100%",
@ -102,7 +103,7 @@ export const Login: FC<LoginProps> = ({ session }) => {
fontWeight: 700,
mt: 1,
ml: 0.5,
fontFamily: "Poppins",
fontFamily: poppins.style.fontFamily,
}}
>
CDR Link

View file

@ -12,7 +12,7 @@ export const InternalLayout: FC<PropsWithChildren> = ({ children }) => {
<Sidebar open={open} setOpen={setOpen} />
<Grid
item
sx={{ ml: open ? "270px" : "100px", width: "100%", height: "100vh" }}
sx={{ ml: open ? "270px" : "70px", width: "100%", height: "100vh" }}
>
{children as any}
</Grid>

View file

@ -3,7 +3,7 @@ import { usePathname, useRouter } from "next/navigation";
import useSWR from "swr";
import { Grid, Box, TextField, Autocomplete } from "@mui/material";
import { searchQuery } from "@/app/_graphql/searchQuery";
import { colors } from "@/app/_styles/theme";
import { colors } from "ui";
type SearchResultProps = {
props: any;

View file

@ -35,10 +35,10 @@ import LinkLogo from "public/link-logo-small.png";
import { useSession, signOut } from "next-auth/react";
import { getTicketOverviewCountsQuery } from "app/_graphql/getTicketOverviewCountsQuery";
import { SearchBox } from "./SearchBox";
import { fonts } from "app/_styles/theme";
import { fonts } from "ui";
const openWidth = 270;
const closedWidth = 100;
const closedWidth = 70;
const MenuItem = ({
name,
@ -49,6 +49,7 @@ const MenuItem = ({
selected = false,
open = true,
badge,
depth = 0,
target = "_self",
}: any) => {
const { roboto } = fonts;
@ -90,8 +91,8 @@ const MenuItem = ({
width: 30,
height: "28px",
position: "relative",
ml: "9px",
mr: "1px",
ml: "8px",
mr: "2px",
}}
>
<Box
@ -114,9 +115,21 @@ const MenuItem = ({
border: "solid 1px #fff",
borderColor: "transparent transparent transparent #fff",
borderRadius: "60px",
rotate: "-35deg",
rotate: "-50deg",
}}
/>
{depth > 0 && (
<Box
sx={{
width: depth * 22,
height: "1px",
backgroundColor: "white",
position: "absolute",
left: "26px",
top: "14px",
}}
/>
)}
</Box>
)}
{open && (
@ -132,6 +145,7 @@ const MenuItem = ({
border: 0,
textAlign: "left",
color: "white",
ml: depth * 3,
}}
>
{name}
@ -220,7 +234,7 @@ export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
<Box
sx={{
position: "absolute",
top: 20,
top: 24,
right: open ? -8 : -16,
color: "#1C75FD",
rotate: open ? "90deg" : "-90deg",
@ -231,8 +245,8 @@ export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
>
<ExpandCircleDownIcon
sx={{
width: 30,
height: 30,
width: 24,
height: 24,
background: "white",
borderRadius: 500,
}}
@ -338,9 +352,7 @@ export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
}}
/>
</Grid>
<Grid item>
<SearchBox />
</Grid>
<Grid item>{open && <SearchBox />}</Grid>
<Grid
item
container
@ -558,7 +570,7 @@ export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
<>
<MenuItem
name="Admin"
href="/admin/zammad"
href="/admin/bridge"
Icon={SettingsIcon}
iconSize={20}
open={open}
@ -570,6 +582,56 @@ export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
onClick={undefined}
>
<List component="div" disablePadding>
<MenuItem
name="CDR Bridge"
href="/admin/bridge"
selected={pathname.endsWith("/admin/bridge")}
open={open}
/>
<Collapse
in={pathname.startsWith("/admin/bridge")}
timeout="auto"
unmountOnExit
onClick={undefined}
>
<List component="div" disablePadding>
<MenuItem
name="WhatsApp"
href="/admin/bridge/whatsapp"
depth={1}
selected={pathname.endsWith("/admin/bridge/whatsapp")}
open={open}
/>
<MenuItem
name="Signal"
href="/admin/bridge/signal"
depth={1}
selected={pathname.endsWith("/admin/bridge/signal")}
open={open}
/>
<MenuItem
name="Facebook"
href="/admin/bridge/facebook"
depth={1}
selected={pathname.endsWith("/admin/bridge/facebook")}
open={open}
/>
<MenuItem
name="Voice"
href="/admin/bridge/voice"
depth={1}
selected={pathname.endsWith("/admin/bridge/voice")}
open={open}
/>
<MenuItem
name="Webhooks"
href="/admin/bridge/webhooks"
depth={1}
selected={pathname.endsWith("/admin/bridge/webhooks")}
open={open}
/>
</List>
</Collapse>
<MenuItem
name="Zammad Settings"
href="/admin/zammad"
@ -578,16 +640,6 @@ export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
selected={pathname.endsWith("/admin/zammad")}
open={open}
/>
{false && roles.includes("bridge") && (
<MenuItem
name="Bridge"
href="/admin/bridge"
Icon={FeaturedPlayListIcon}
iconSize={0}
selected={pathname.endsWith("/admin/bridge")}
open={open}
/>
)}
{roles.includes("label_studio") && (
<MenuItem
name="Label Studio"

View file

@ -0,0 +1,11 @@
import { Create } from "bridge-ui";
type PageProps = {
params: { segment: string[] };
};
export default function Page({ params: { segment } }: PageProps) {
const service = segment[0];
return <Create service={service} />;
}

View file

@ -0,0 +1,27 @@
import { db } from "bridge-common";
import { serviceConfig, Detail } from "bridge-ui";
type Props = {
params: { segment: string[] };
};
export default async function Page({ params: { segment } }: Props) {
const service = segment[0];
const id = segment?.[1];
if (!id) return null;
const {
[service]: { table },
} = serviceConfig;
const row = await db
.selectFrom(table)
.selectAll()
.where("id", "=", id)
.executeTakeFirst();
if (!row) return null;
return <Detail service={service} row={row} />;
}

View file

@ -0,0 +1,27 @@
import { db } from "bridge-common";
import { serviceConfig, Edit } from "bridge-ui";
type PageProps = {
params: { segment: string[] };
};
export default async function Page({ params: { segment } }: PageProps) {
const service = segment[0];
const id = segment?.[1];
if (!id) return null;
const {
[service]: { table },
} = serviceConfig;
const row = await db
.selectFrom(table)
.selectAll()
.where("id", "=", id)
.executeTakeFirst();
if (!row) return null;
return <Edit service={service} row={row} />;
}

View file

@ -0,0 +1,3 @@
import { ServiceLayout } from "bridge-ui";
export default ServiceLayout;

View file

@ -0,0 +1,22 @@
import { db } from "bridge-common";
import { serviceConfig, List } from "bridge-ui";
type PageProps = {
params: {
segment: string[];
};
};
export default async function Page({ params: { segment } }: PageProps) {
const service = segment[0];
if (!service) return null;
const config = serviceConfig[service];
if (!config) return null;
const rows = await db.selectFrom(config.table).selectAll().execute();
return <List service={service} rows={rows} />;
}

View file

@ -1,6 +1,5 @@
// import { Admin } from "./_components/Admin";
import { Box } from "@mui/material";
import { Home } from "bridge-ui";
export default function Page() {
return <Box />;
return <Home />;
}

View file

@ -11,7 +11,10 @@ export const DocsWrapper: FC = () => (
sx={{ height: "100%", width: "100%" }}
direction="column"
>
<Grid item sx={{ height: "100vh", width: "100%" }}>
<Grid
item
sx={{ height: "calc(100vh + 120px)", width: "100%", mt: "-121px" }}
>
<Iframe
id="docs"
url={"https://digiresilience.org/docs/link/about/"}

View file

@ -5,7 +5,7 @@ export const dynamic = "force-dynamic";
export default async function Page() {
return (
<LeafcutterWrapper>
<Home visualizations={{}} />
<Home visualizations={[]} showWelcome={false} />
</LeafcutterWrapper>
);
}

View file

@ -1,17 +1,11 @@
"use client";
import { FC, useState } from "react";
import {
Grid,
Button,
Dialog,
DialogActions,
DialogContent,
TextField,
Autocomplete,
} from "@mui/material";
import useSWR, { useSWRConfig } from "swr";
import { createTicketMutation } from "app/_graphql/createTicketMutation";
import { FC } from "react";
import { useFormState } from "react-dom";
import { Grid } from "@mui/material";
import { Dialog, Button, TextField, Autocomplete } from "ui";
import { createTicketAction } from "app/_actions/tickets";
import useSWR from "swr";
interface TicketCreateDialogProps {
open: boolean;
@ -22,133 +16,83 @@ export const TicketCreateDialog: FC<TicketCreateDialogProps> = ({
open,
closeDialog,
}) => {
const [kind, setKind] = useState("note");
const [customerID, setCustomerID] = useState("");
const [groupID, setGroupID] = useState("");
const [ownerID, setOwnerID] = useState("");
const [priorityID, setPriorityID] = useState("");
const [stateID, setStateID] = useState("");
const [tags, setTags] = useState([]);
const [title, setTitle] = useState("");
const [body, setBody] = useState("");
const backgroundColor = "#1982FC";
const color = "white";
const { fetcher } = useSWRConfig();
const ticket = {
customerId: customerID,
groupId: groupID,
ownerId: ownerID,
priorityId: priorityID,
stateId: stateID,
tags,
title,
const initialState = {
messages: [],
errors: [],
values: {
customerId: "",
groupId: "",
ownerId: "",
priorityId: "",
stateId: "",
tags: [],
title: "",
article: {
body,
type: kind,
internal: kind === "note",
body: "",
type: "note",
internal: true,
},
},
};
const [formState, formAction] = useFormState(
createTicketAction,
initialState,
);
const { data: users, error: usersError }: any = useSWR({
url: "/api/v1/users",
method: "GET",
});
console.log({ users, usersError });
const customers =
users?.filter((user: any) => user.role_ids.includes(3)) ?? [];
const formattedCustomers = customers.map((customer: any) => ({
label: customer.login,
id: `${customer.id}`,
}));
const createTicket = async () => {
await fetcher({
document: createTicketMutation,
variables: {
input: {
ticket,
},
},
});
closeDialog();
setBody("");
};
return (
<Dialog open={open} maxWidth="md" fullWidth>
<DialogContent>
<Grid container direction="column" spacing={2}>
<Grid item>
<TextField
label={"Title"}
fullWidth
value={title}
onChange={(e: any) => setTitle(e.target.value)}
/>
</Grid>
<Grid item>
<Autocomplete
disablePortal
options={formattedCustomers}
value={customerID}
sx={{ width: 300 }}
onChange={(e: any) => setCustomerID(e.target.value.id)}
renderInput={(params) => (
<TextField {...params} label="Customer" />
)}
/>
</Grid>
<Grid item>
<TextField
label={"Details"}
multiline
rows={10}
fullWidth
value={body}
onChange={(e: any) => setBody(e.target.value)}
/>
</Grid>
</Grid>
</DialogContent>
<DialogActions sx={{ px: 3, pt: 0, pb: 3 }}>
<Dialog
title="Create Ticket"
open={open}
onClose={closeDialog}
formAction={formAction}
buttons={
<Grid container justifyContent="space-between">
<Grid item>
<Button
sx={{
backgroundColor: "white",
color: "#666",
fontFamily: "Poppins, sans-serif",
fontWeight: 700,
borderRadius: 2,
textTransform: "none",
}}
text="Cancel"
kind="secondary"
onClick={() => {
setBody("");
closeDialog();
}}
>
Cancel
</Button>
/>
</Grid>
<Grid item>
<Button
sx={{
backgroundColor,
color,
fontFamily: "Poppins, sans-serif",
fontWeight: 700,
borderRadius: 2,
textTransform: "none",
px: 3,
}}
onClick={createTicket}
<Button text="Save" type="submit" kind="primary" />
</Grid>
</Grid>
}
>
Create Ticket
</Button>
<Grid container direction="column" spacing={3}>
<Grid item>
<Autocomplete
name="customerId"
label="Customer"
options={formattedCustomers}
formState={formState}
/>
</Grid>
<Grid item>
<TextField name="title" label="Title" formState={formState} />
</Grid>
<Grid item>
<TextField
name="details"
label="Details"
lines={10}
formState={formState}
/>
</Grid>
</Grid>
</DialogActions>
</Dialog>
);
};

View file

@ -4,8 +4,7 @@ import { FC, useState } from "react";
import { Grid, Box } from "@mui/material";
import { GridColDef } from "@mui/x-data-grid-pro";
import { StyledDataGrid } from "app/(main)/_components/StyledDataGrid";
import { Button } from "app/_components/Button";
import { typography } from "app/_styles/theme";
import { Button, List, typography } from "ui";
import { useRouter } from "next/navigation";
import { TicketCreateDialog } from "./TicketCreateDialog";
@ -26,83 +25,61 @@ export const TicketList: FC<TicketListProps> = ({ title, tickets }) => {
{
field: "title",
headerName: "Title",
flex: 5,
flex: 3,
},
{
field: "customer",
headerName: "Sender",
valueGetter: (params: any) => params.row?.customer?.fullname,
flex: 2,
valueGetter: (value: any) => value?.fullname,
flex: 1,
},
{
field: "createdAt",
headerName: "Created At",
valueGetter: (params: any) =>
new Date(params.row?.createdAt).toLocaleString(),
valueGetter: (value: any) => new Date(value).toLocaleString(),
flex: 1,
},
{
field: "updatedAt",
headerName: "Updated At",
valueGetter: (params: any) =>
new Date(params.row?.updatedAt).toLocaleString(),
valueGetter: (value: any) => new Date(value).toLocaleString(),
flex: 1,
},
{
field: "group",
headerName: "Group",
valueGetter: (params: any) => params.row?.group?.name,
valueGetter: (value: any) => value?.name,
flex: 1,
},
];
const rowClick = ({ row }) => {
router.push(`/tickets/${row.internalId}`);
const onRowClick = (id: any) => {
router.push(`/tickets/${id}`);
};
return (
<>
<Box sx={{ height: "100vh", backgroundColor: "#ddd", p: 3 }}>
<Grid container direction="column">
<Grid
item
container
direction="row"
justifyContent="space-between"
alignItems="center"
>
<Grid item>
<Box
sx={{
backgroundColor: "#ddd",
px: "8px",
pb: "16px",
...typography.h4,
fontSize: 24,
<List
title={title}
rows={tickets}
columns={gridColumns}
onRowClick={onRowClick}
getRowID={(row: any) => {
console.log({ row });
return row.internalId;
}}
>
{title}
</Box>
</Grid>
buttons={
<Grid container direction="row-reverse" alignItems="center">
<Grid item>
<Button
href={""}
onClick={() => setDialogOpen(true)}
text="Create"
color="#1982FC"
color="primary"
/>
</Grid>
</Grid>
<Grid item>
<StyledDataGrid
name={title}
columns={gridColumns}
rows={tickets}
onRowClick={rowClick}
}
/>
</Grid>
</Grid>
</Box>
<TicketCreateDialog
open={dialogOpen}
closeDialog={() => setDialogOpen(false)}

View file

@ -108,6 +108,6 @@ export const ZammadOverview: FC<ZammadOverviewProps> = ({ name }) => {
}, [name]);
const shouldRender = tickets && !error;
console.log({ shouldRender, tickets, error });
return shouldRender && <TicketList title={name} tickets={tickets} />;
};

View file

@ -1,18 +1,12 @@
"use client";
import { FC, useEffect, useState } from "react";
import { FC, useState } from "react";
import useSWR from "swr";
import { getTicketQuery } from "app/_graphql/getTicketQuery";
import { getTicketArticlesQuery } from "app/_graphql/getTicketArticlesQuery";
import {
Grid,
Box,
Typography,
Button,
// Dialog,
// DialogActions,
// DialogContent,
} from "@mui/material";
import { Grid, Box, Typography } from "@mui/material";
import { Button, fonts, colors } from "ui";
import "@chatscope/chat-ui-kit-styles/dist/default/styles.min.css";
import {
MainContainer,
@ -28,6 +22,8 @@ interface TicketDetailProps {
}
export const TicketDetail: FC<TicketDetailProps> = ({ id }) => {
const { poppins, roboto } = fonts;
const { veryLightGray, lightGray } = colors;
const [dialogOpen, setDialogOpen] = useState(false);
const [articleKind, setArticleKind] = useState("note");
const { data: ticketData, error: ticketError }: any = useSWR(
@ -52,7 +48,6 @@ export const TicketDetail: FC<TicketDetailProps> = ({ id }) => {
});
const closeDialog = () => setDialogOpen(false);
console.log({ recentViewData, recentViewError });
const ticket = ticketData?.ticket;
const ticketArticles = ticketArticlesData?.ticketArticles;
@ -79,13 +74,19 @@ export const TicketDetail: FC<TicketDetailProps> = ({ id }) => {
>
<Typography
variant="h5"
sx={{ fontFamily: "Poppins", fontWeight: 700 }}
sx={{
fontFamily: poppins.style.fontFamily,
fontWeight: 700,
}}
>
{ticket.title}
</Typography>
<Typography
variant="h6"
sx={{ fontFamily: "Roboto", fontWeight: 400 }}
sx={{
fontFamily: roboto.style.fontFamily,
fontWeight: 400,
}}
>{`Ticket #${ticket.number} (created ${new Date(
ticket.createdAt,
).toLocaleDateString()})`}</Typography>
@ -118,8 +119,8 @@ export const TicketDetail: FC<TicketDetailProps> = ({ id }) => {
<Box
sx={{
height: 80,
background: "#eeeeee",
borderTop: "1px solid #ddd",
background: veryLightGray,
borderTop: `1px solid ${lightGray}`,
position: "absolute",
bottom: 0,
width: "100%",
@ -128,59 +129,31 @@ export const TicketDetail: FC<TicketDetailProps> = ({ id }) => {
>
<Grid
container
spacing={4}
spacing={6}
justifyContent="center"
alignItems="center"
alignContent="center"
sx={{ height: "100%", pt: 6 }}
>
<Grid item>
<Button
variant="contained"
disableElevation
sx={{
fontFamily: "Poppins, sans-serif",
fontWeight: 700,
borderRadius: 2,
textTransform: "none",
backgroundColor: "#1982FC",
padding: "6px 30px",
margin: "20px 0px",
whiteSpace: "nowrap",
py: "10px",
mt: 2,
}}
onClick={() => {
setArticleKind(firstArticleKind);
setDialogOpen(true);
}}
>
Reply to ticket
</Button>
</Grid>
<Grid item>
<Button
variant="contained"
disableElevation
sx={{
fontFamily: "Poppins, sans-serif",
fontWeight: 700,
borderRadius: 2,
textTransform: "none",
color: "black",
backgroundColor: "#FFB620",
padding: "6px 30px",
margin: "20px 0px",
whiteSpace: "nowrap",
py: "10px",
mt: 2,
}}
text="Write note to agent"
color="#FFB620"
onClick={() => {
setArticleKind("note");
setDialogOpen(true);
}}
>
Write note to agent
</Button>
/>
</Grid>
<Grid item>
<Button
text="Reply to ticket"
kind="primary"
onClick={() => {
setArticleKind(firstArticleKind);
setDialogOpen(true);
}}
/>
</Grid>
</Grid>
</Box>

View file

@ -1,35 +1,23 @@
"use client";
import { FC, useEffect, useState } from "react";
import {
Grid,
Box,
// Typography,
// TextField,
// Stack,
Select,
MenuItem,
} from "@mui/material";
import { Grid, Box, MenuItem } from "@mui/material";
import { useFormState } from "react-dom";
import { Select, Button } from "ui";
import { MuiChipsInput } from "mui-chips-input";
import useSWR, { useSWRConfig } from "swr";
import { getTicketQuery } from "app/_graphql/getTicketQuery";
import { updateTicketMutation } from "app/_graphql/updateTicketMutation";
import { updateTagsMutation } from "app/_graphql/updateTagsMutation";
import "@chatscope/chat-ui-kit-styles/dist/default/styles.min.css";
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
import { updateTicketAction } from "app/_actions/tickets";
interface TicketEditProps {
id: string;
}
export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
const [selectedGroup, setSelectedGroup] = useState("");
const [selectedOwner, setSelectedOwner] = useState("");
const [selectedPriority, setSelectedPriority] = useState("");
const [selectedState, setSelectedState] = useState("");
const [pendingDate, setPendingDate] = useState(new Date());
const [pendingVisible, setPendingVisible] = useState(false);
const [selectedTags, setSelectedTags] = useState([]);
const selectedTags = [];
const pendingVisible = false;
const pendingDate = new Date();
const handleDelete = () => {
console.info("You clicked the delete icon.");
};
@ -58,24 +46,28 @@ export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
const ticket = ticketData?.ticket;
if (ticket) {
const groupID = ticket.group.id?.split("/").pop();
setSelectedGroup(groupID);
// setSelectedGroup(groupID);
const ownerID = ticket.owner.id?.split("/").pop();
setSelectedOwner(ownerID);
// setSelectedOwner(ownerID);
const priorityID = ticket.priority.id?.split("/").pop();
setSelectedPriority(priorityID);
// setSelectedPriority(priorityID);
const stateID = ticket.state.id?.split("/").pop();
setSelectedState(stateID);
setSelectedTags(ticket.tags);
// setSelectedState(stateID);
// setSelectedTags(ticket.tags);
}
}, [ticketData, ticketError]);
/*
useEffect(() => {
const stateName = filteredStates?.find(
(state: any) => state.id === selectedState,
)?.name;
setPendingVisible(stateName?.includes("pending") ?? false);
}, [selectedState]);
*/
const updateTicket = async (input: any) => {
/*
console.log({ input });
const res = await fetcher({
document: updateTicketMutation,
@ -85,8 +77,10 @@ export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
},
});
console.log({ res });
*/
};
const updateTags = async (tags: string[]) => {
/*
console.log({ tags });
const res = await fetcher({
document: updateTagsMutation,
@ -96,18 +90,50 @@ export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
},
});
console.log({ res });
*/
};
const initialState = {
messages: [],
errors: [],
values: {
customer: "",
group: "",
owner: "",
priority: "",
state: "",
tags: [],
title: "",
article: {
body: "",
type: "note",
internal: true,
},
},
};
const [formState, formAction] = useFormState(
updateTicketAction,
initialState,
);
const shouldRender = ticketData && !ticketError;
return (
shouldRender && (
<Box sx={{ height: "100vh", background: "#ddd", p: 2 }}>
<form action={formAction}>
<Grid container direction="column" spacing={3}>
<Grid item>
<Box sx={{ m: 1 }}>Group</Box>
<Select
defaultValue={selectedGroup}
value={selectedGroup}
name="group"
label="Group"
formState={formState}
getOptions={() =>
groups?.map((group: any) => ({
value: group.id,
label: group.name,
})) ?? []
}
/*
onChange={(e: any) => {
const newGroup = e.target.value;
setSelectedGroup(newGroup);
@ -115,45 +141,43 @@ export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
groupId: `gid://zammad/Group/${newGroup}`,
});
}}
size="small"
sx={{
width: "100%",
backgroundColor: "white",
}}
>
{groups?.map((group: any) => (
<MenuItem key={group.id} value={`${group.id}`}>
{group.name}
</MenuItem>
))}
</Select>
*/
/>
</Grid>
<Grid item>
<Box sx={{ m: 1, mt: 0 }}>Owner</Box>
<Select
value={selectedOwner}
name="owner"
label="Owner"
formState={formState}
getOptions={() =>
agents?.map((user: any) => ({
value: user.id,
label: `${user.firstname} ${user.lastname}`,
})) ?? []
}
/*
onChange={(e: any) => {
const newOwner = e.target.value;
setSelectedOwner(newOwner);
updateTicket({ ownerId: `gid://zammad/User/${newOwner}` });
}}
size="small"
sx={{
width: "100%",
backgroundColor: "white",
}}
>
{agents?.map((user: any) => (
<MenuItem key={user.id} value={`${user.id}`}>
{user.firstname} {user.lastname}
</MenuItem>
))}
</Select>
*/
/>
</Grid>
<Grid item xs={12}>
<Box sx={{ m: 1, mt: 0 }}>State</Box>
<Select
value={selectedState}
name="state"
label="State"
formState={formState}
getOptions={() =>
filteredStates?.map((state: any) => ({
value: state.id,
label: state.name,
})) ?? []
}
/*
onChange={(e: any) => {
const newState = e.target.value;
setSelectedState(newState);
@ -162,64 +186,57 @@ export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
pendingTime: pendingDate.toISOString(),
});
}}
size="small"
sx={{
width: "100%",
backgroundColor: "white",
}}
>
{filteredStates?.map((state: any) => (
<MenuItem key={state.id} value={state.id}>
{state.name}
</MenuItem>
))}
</Select>
*/
/>
</Grid>
<Grid
item
xs={12}
sx={{ display: pendingVisible ? "inherit" : "none" }}
>
<DatePicker
{/* <DatePicker
label="Pending Date"
value={pendingDate}
onChange={(newValue: any) => {
console.log(newValue);
setPendingDate(newValue);
updateTicket({
pendingTime: newValue.toISOString(),
});
})
}}
slotProps={{ textField: { size: "small" } }}
sx={{
width: "100%",
backgroundColor: "white",
}}
/>
/> */}
</Grid>
<Grid item>
<Box sx={{ m: 1, mt: 0 }}>Priority</Box>
<Select
value={selectedPriority}
name="priority"
label="Priority"
formState={formState}
getOptions={() =>
priorities?.map((priority: any) => ({
value: priority.id,
label: priority.name,
})) ?? []
}
/*
onChange={(e: any) => {
const newPriority = e.target.value;
setSelectedPriority(newPriority);
updateTicket({
priorityId: `gid://zammad/Ticket::Priority/${newPriority}`,
});
}}
size="small"
sx={{
width: "100%",
backgroundColor: "white",
}}
>
{priorities?.map((priority: any) => (
<MenuItem key={priority.id} value={priority.id}>
{priority.name}
</MenuItem>
))}
</Select>
*/
/>
</Grid>
<Grid item>
@ -228,12 +245,20 @@ export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
sx={{ backgroundColor: "white", width: "100%" }}
value={selectedTags}
onChange={(tags: any) => {
/*
setSelectedTags(tags);
updateTags(tags);
*/
}}
/>
</Grid>
<Grid item container direction="row-reverse">
<Grid item>
<Button text="Save" kind="primary" type="submit" />
</Grid>
</Grid>
</Grid>
</form>
</Box>
)
);

View file

@ -0,0 +1,113 @@
"use server";
import { revalidatePath } from "next/cache";
import { createTicketMutation } from "app/_graphql/createTicketMutation";
import { updateTicketMutation } from "app/_graphql/updateTicketMutation";
import { updateTagsMutation } from "app/_graphql/updateTagsMutation";
// import { executeMutation } from "app/_lib/graphql";
// import { AddAssetMutation } from "../_graphql/AddAssetMutation";
export const createTicketAction = async (
currentState: any,
formData: FormData,
) => {
/*
const createTicket = async () => {
await fetcher({
document: createTicketMutation,
variables: {
input: {
ticket,
},
},
});
closeDialog();
setBody("");
};
*/
try {
const ticket = {
title: formData.get("title"),
};
const result = await executeMutation({
project,
mutation: AddAssetMutation,
variables: {
input: asset,
},
});
revalidatePath(`/${project}/assets`);
return {
...currentState,
values: { ...asset, ...result.addAsset, project },
success: true,
};
} catch (e: any) {
return { success: false, message: e?.message ?? "Unknown error" };
}
};
export const updateTicketAction = async (
currentState: any,
formData: FormData,
) => {
try {
const { id, project } = currentState.values;
const updatedTicket = {
title: formData.get("title"),
};
await executeMutation({
project,
mutation: UpdateAssetMutation,
variables: {
id,
input: updatedAsset,
},
});
revalidatePath(`/${project}/assets/${id}`);
return {
...currentState,
values: { ...currentState.values, ...updatedAsset, id, project },
success: true,
};
} catch (e: any) {
return { success: false, message: e?.message ?? "Unknown error" };
}
};
export const updateTicketTagsAction = async (
currentState: any,
formData: FormData,
) => {
try {
const { id, project } = currentState.values;
const updatedTicket = {
title: formData.get("title"),
};
await executeMutation({
project,
mutation: UpdateAssetMutation,
variables: {
id,
input: updatedAsset,
},
});
revalidatePath(`/${project}/assets/${id}`);
return {
...currentState,
values: { ...currentState.values, ...updatedAsset, id, project },
success: true,
};
} catch (e: any) {
return { success: false, message: e?.message ?? "Unknown error" };
}
};

View file

@ -0,0 +1,3 @@
"use server";
const fetchUsersAction = async () => {};

View file

@ -104,23 +104,19 @@ export const MultiProvider: FC<PropsWithChildren> = ({ children }) => {
};
return (
<>
<CssBaseline />
<NextAppDirEmotionCacheProvider options={{ key: "css" }}>
<CssBaseline />
<SWRConfig value={{ fetcher: multiFetcher }}>
<SessionProvider>
<CookiesProvider>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<I18n locale={locale} messages={messages[locale]}>
<LeafcutterProvider>
{children}
</LeafcutterProvider>
<LeafcutterProvider>{children}</LeafcutterProvider>
</I18n>
</LocalizationProvider>
</CookiesProvider>
</SessionProvider>
</SWRConfig>
</NextAppDirEmotionCacheProvider>
</>
);
};

View file

@ -1,113 +0,0 @@
import { Roboto, Playfair_Display, Poppins } from "next/font/google";
const roboto = Roboto({
weight: ["400"],
subsets: ["latin"],
display: "swap",
});
const playfair = Playfair_Display({
weight: ["900"],
subsets: ["latin"],
display: "swap",
});
const poppins = Poppins({
weight: ["400", "700"],
subsets: ["latin"],
display: "swap",
});
export const fonts = {
roboto,
playfair,
poppins,
};
export const colors: any = {
lightGray: "#ededf0",
mediumGray: "#e3e5e5",
darkGray: "#33302f",
mediumBlue: "#4285f4",
green: "#349d7b",
lavender: "#a5a6f6",
darkLavender: "#5d5fef",
pink: "#fcddec",
cdrLinkOrange: "#ff7115",
coreYellow: "#fac942",
helpYellow: "#fff4d5",
dwcDarkBlue: "#191847",
hazyMint: "#ecf7f8",
leafcutterElectricBlue: "#4d6aff",
leafcutterLightBlue: "#fafbfd",
waterbearElectricPurple: "#332c83",
waterbearLightSmokePurple: "#eff3f8",
bumpedPurple: "#212058",
mutedPurple: "#373669",
warningPink: "#ef5da8",
lightPink: "#fff0f7",
lightGreen: "#f0fff3",
lightOrange: "#fff5f0",
beige: "#f6f2f1",
almostBlack: "#33302f",
white: "#ffffff",
};
export const typography: any = {
h1: {
fontFamily: playfair.style.fontFamily,
fontSize: 45,
fontWeight: 700,
lineHeight: 1.1,
margin: 0,
},
h2: {
fontFamily: poppins.style.fontFamily,
fontSize: 35,
fontWeight: 700,
lineHeight: 1.1,
margin: 0,
},
h3: {
fontFamily: poppins.style.fontFamily,
fontWeight: 400,
fontSize: 27,
lineHeight: 1.1,
margin: 0,
},
h4: {
fontFamily: poppins.style.fontFamily,
fontWeight: 700,
fontSize: 18,
},
h5: {
fontFamily: roboto.style.fontFamily,
fontWeight: 700,
fontSize: 16,
lineHeight: "24px",
textTransform: "uppercase",
textAlign: "center",
margin: 1,
},
h6: {
fontFamily: roboto.style.fontFamily,
fontWeight: 400,
fontSize: 14,
textAlign: "center",
},
p: {
fontFamily: roboto.style.fontFamily,
fontSize: 17,
lineHeight: "26.35px",
fontWeight: 400,
margin: 0,
},
small: {
fontFamily: roboto.style.fontFamily,
fontSize: 13,
lineHeight: "18px",
fontWeight: 400,
margin: 0,
},
};

View file

@ -20,11 +20,12 @@
"@mui/material": "^5",
"@mui/x-data-grid-pro": "^7.3.2",
"@mui/x-date-pickers-pro": "^7.3.2",
"bridge-common": "*",
"bridge-ui": "*",
"date-fns": "^3.6.0",
"graphql-request": "^6.1.0",
"leafcutter-ui": "*",
"material-ui-popup-state": "^5.1.0",
"bridge-ui": "*",
"mui-chips-input": "^2.1.4",
"next": "14.2.3",
"next-auth": "^4.24.7",

View file

@ -5,7 +5,7 @@ services:
container_name: leafcutter
restart: ${RESTART}
build:
context: ../../
context: .
dockerfile: ../../apps/leafcutter/Dockerfile
image: registry.gitlab.com/digiresilience/link/link-stack/leafcutter:${LINK_STACK_VERSION}
expose:

View file

@ -5,7 +5,7 @@ services:
container_name: link
restart: ${RESTART}
build:
context: ../../
context: .
dockerfile: ../../apps/link/Dockerfile
image: registry.gitlab.com/digiresilience/link/link-stack/link:${LINK_STACK_VERSION}
expose:

View file

@ -1 +1 @@
FROM memcached:1.6.23-bookworm
FROM memcached:1.6.27-bookworm

View file

@ -1 +1 @@
FROM nginxproxy/nginx-proxy:1.5.1
FROM nginxproxy/nginx-proxy:1.5.2

View file

@ -1 +1 @@
FROM opensearchproject/opensearch-dashboards:2.12.0
FROM opensearchproject/opensearch-dashboards:2.13.0

View file

@ -1,2 +1,2 @@
FROM opensearchproject/opensearch:2.12.0
FROM opensearchproject/opensearch:2.13.0
RUN /usr/share/opensearch/bin/opensearch-plugin install ingest-attachment -b

View file

@ -1 +1 @@
FROM bbernhard/signal-cli-rest-api:0.81
FROM bbernhard/signal-cli-rest-api:0.83

View file

@ -1,4 +1,6 @@
ARG ZAMMAD_VERSION=6.2.0
# need to include changes from https://github.com/zammad/zammad/blob/506c295c1d15f8dc19fc8bb69af1fb721bf10f49/contrib/docker/setup.sh
ARG ZAMMAD_VERSION=6.3.0
FROM node:16.18.0-slim as node
FROM zammad/zammad-docker-compose:${ZAMMAD_VERSION} AS builder

View file

@ -8,6 +8,7 @@ import { Button, Dialog, TextField, Select, MultiValueField } from "ui";
import { generateCreateAction } from "../lib/actions";
import { FieldDescription } from "../lib/service";
import { serviceConfig } from "../config/config";
import { getBasePath } from "../lib/frontendUtils";
type CreateProps = {
service: string;
@ -51,7 +52,7 @@ export const Create: FC<CreateProps> = ({ service }) => {
useEffect(() => {
if (formState.success) {
router.push(`/${entity}/${formState.values.id}`);
router.push(`${getBasePath()}${entity}/${formState.values.id}`);
}
}, [formState.success, router, entity, formState.values.id]);
@ -60,14 +61,14 @@ export const Create: FC<CreateProps> = ({ service }) => {
open
title={`Create ${displayName}`}
formAction={formAction}
onClose={() => router.push(`/${entity}`)}
onClose={() => router.push(`${getBasePath()}${entity}`)}
buttons={
<Grid container justifyContent="space-between">
<Grid item>
<Button
text="Cancel"
kind="secondary"
onClick={() => router.push(`/${entity}`)}
onClick={() => router.push(`${getBasePath()}${entity}`)}
/>
</Grid>
<Grid item>

View file

@ -9,6 +9,7 @@ import { type Database } from "bridge-common";
import { generateDeleteAction } from "../lib/actions";
import { serviceConfig } from "../config/config";
import { FieldDescription } from "../lib/service";
import { getBasePath } from "../lib/frontendUtils";
type DetailProps = {
service: string;
@ -29,7 +30,7 @@ export const Detail: FC<DetailProps> = ({ service, row }) => {
const continueDeleteAction = async () => {
await deleteAction?.(id);
setShowDeleteConfirmation(false);
router.push(`/${entity}`);
router.push(`${getBasePath()}${entity}`);
};
return (
@ -37,7 +38,7 @@ export const Detail: FC<DetailProps> = ({ service, row }) => {
<Dialog
open
title={`${displayName} Detail`}
onClose={() => router.push(`/${entity}`)}
onClose={() => router.push(`${getBasePath()}${entity}`)}
buttons={
<Grid container justifyContent="space-between">
<Grid item container xs="auto" spacing={2}>
@ -52,12 +53,16 @@ export const Detail: FC<DetailProps> = ({ service, row }) => {
<Button
text="Edit"
kind="secondary"
href={`/${entity}/${id}/edit`}
href={`${getBasePath()}${entity}/${id}/edit`}
/>
</Grid>
</Grid>
<Grid item>
<Button text="Done" kind="primary" href={`/${entity}`} />
<Button
text="Done"
kind="primary"
href={`${getBasePath()}${entity}`}
/>
</Grid>
</Grid>
}

View file

@ -10,6 +10,7 @@ import { type Database } from "bridge-common";
import { generateUpdateAction } from "../lib/actions";
import { serviceConfig } from "../config/config";
import { FieldDescription } from "../lib/service";
import { getBasePath } from "../lib/frontendUtils";
type EditProps = {
service: string;
@ -51,7 +52,7 @@ export const Edit: FC<EditProps> = ({ service, row }) => {
useEffect(() => {
if (formState.success) {
router.push(`/${entity}`);
router.push(`${getBasePath()}${entity}`);
}
}, [formState.success, router, entity]);
@ -60,14 +61,14 @@ export const Edit: FC<EditProps> = ({ service, row }) => {
open
title={`Edit ${displayName}`}
formAction={formAction}
onClose={() => router.push(`/${entity}`)}
onClose={() => router.push(`${getBasePath()}${entity}`)}
buttons={
<Grid container justifyContent="space-between">
<Grid item>
<Button
text="Cancel"
kind="secondary"
onClick={() => router.push(`/${entity}`)}
onClick={() => router.push(`${getBasePath()}${entity}`)}
/>
</Grid>
<Grid item>

View file

@ -0,0 +1,5 @@
import { FC } from "react";
export const Home: FC = () => {
return <h1>Home</h1>;
};

View file

@ -6,6 +6,7 @@ import { List as InternalList, Button } from "ui";
import { type Selectable } from "kysely";
import { type Database } from "bridge-common";
import { serviceConfig } from "../config/config";
import { getBasePath } from "../lib/frontendUtils";
type ListProps = {
service: string;
@ -18,7 +19,7 @@ export const List: FC<ListProps> = ({ service, rows }) => {
const router = useRouter();
const onRowClick = (id: string) => {
router.push(`/${entity}/${id}`);
router.push(`${getBasePath()}${entity}/${id}`);
};
return (
@ -28,7 +29,11 @@ export const List: FC<ListProps> = ({ service, rows }) => {
columns={listColumns}
onRowClick={onRowClick}
buttons={
<Button text="Create" kind="primary" href={`/${entity}/create`} />
<Button
text="Create"
kind="primary"
href={`${getBasePath()}${entity}/create`}
/>
}
/>
);

View file

@ -149,9 +149,14 @@ export const webhooksConfig: ServiceConfig = {
flex: 1,
},
{
field: "description",
headerName: "Description",
flex: 2,
field: "backendType",
headerName: "Type",
flex: 1,
},
{
field: "endpointUrl",
headerName: "Endpoint",
flex: 1,
},
{
field: "updatedAt",

View file

@ -1,3 +1,4 @@
export { Home } from "./components/Home";
export { List } from "./components/List";
export { Create } from "./components/Create";
export { Edit } from "./components/Edit";

View file

@ -0,0 +1,10 @@
export const getBasePath = (): string => {
if (
typeof window !== "undefined" &&
window?.location?.pathname?.includes("/admin/bridge")
) {
return "/admin/bridge/";
}
return "/";
};

File diff suppressed because one or more lines are too long

View file

@ -1,18 +1,27 @@
"use server";
import { performLeafcutterQuery, performZammadQuery, createUserVisualization } from "opensearch-common";
import {
performLeafcutterQuery,
performZammadQuery,
createUserVisualization,
} from "opensearch-common";
export const createUserVisualizationAction = async ({visualizationID, title, description, query}: any) => {
const email = "darren@redaranj.com";
export const createUserVisualizationAction = async ({
visualizationID,
title,
description,
query,
}: any) => {
const email = "xxx@example.com";
const id = await createUserVisualization({
email,
visualizationID,
title,
description,
query
query,
});
return id;
}
};
export const searchVisualizationsAction = async (
kind: string,

View file

@ -3,7 +3,6 @@
import { useEffect, FC } from "react";
import { useRouter, usePathname } from "next/navigation";
import Link from "next/link";
import ReactMarkdown from "react-markdown";
import { Grid, Button } from "@mui/material";
import { useTranslate } from "react-polyglot";
import { useCookies } from "react-cookie";
@ -11,13 +10,18 @@ import { Welcome } from "./Welcome";
import { WelcomeDialog } from "./WelcomeDialog";
import { VisualizationCard } from "./VisualizationCard";
import { useLeafcutterContext } from "./LeafcutterProvider";
import { getBasePath } from "../lib/utils";
type HomeProps = {
visualizations: any;
showWelcome?: boolean;
};
export const Home: FC<HomeProps> = ({ visualizations = [], showWelcome = true }) => {
export const Home: FC<HomeProps> = ({
visualizations = [],
showWelcome = true,
}) => {
console.log("Home", visualizations);
const router = useRouter();
const pathname = usePathname() ?? "";
const cookieName = "homeIntroComplete";
@ -45,7 +49,7 @@ export const Home: FC<HomeProps> = ({ visualizations = [], showWelcome = true })
sx={{ pt: "22px", pb: "22px" }}
direction="row-reverse"
>
<Link href={`${process.env.LEAFCUTTER_BASE_PATH ?? ""}/create`} passHref>
<Link href={`${getBasePath()}/create`} passHref>
<Button
sx={{
fontSize: 14,
@ -81,7 +85,11 @@ export const Home: FC<HomeProps> = ({ visualizations = [], showWelcome = true })
justifyContent="center"
>
<Grid item sx={{ ...h4, width: 450, textAlign: "center" }}>
<ReactMarkdown>{t("noSavedVisualizations")}</ReactMarkdown>
{"You dont have any saved visualizations. Go to "}
<Link href={`${getBasePath()}/create`}>Search and Create</Link>
{" or "}
<Link href={`${getBasePath()}/trends`}>Trends</Link>
{" to get started."}
</Grid>
</Grid>
) : null}

View file

@ -0,0 +1,9 @@
export const getBasePath = (): string => {
const basePath = process.env.LEAFCUTTER_BASE_PATH;
if (basePath && basePath !== "") {
return `${basePath}`;
}
return "";
};

View file

@ -0,0 +1,9 @@
export const getBasePath = (): string => {
const basePath = process.env.NEXT_PUBLIC_LEAFCUTTER_BASE_PATH;
if (basePath && basePath !== "") {
return `${basePath}`;
}
return "";
};

File diff suppressed because one or more lines are too long

View file

@ -329,7 +329,7 @@ export const getUserVisualizations = async (email: string, limit: number) => {
url: getEmbedURL("private", getDocumentID(hit)),
}));
return results;
return []; //results;
};
/* Global */

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,38 @@
import { FC } from "react";
import { TextField, Autocomplete as AutocompleteInternal } from "@mui/material";
import { colors } from "../styles/theme";
type AutocompleteProps = {
name: string;
label: string;
options: any[];
formState: Record<string, any>;
disabled?: boolean;
required?: boolean;
};
export const Autocomplete: FC<AutocompleteProps> = ({
name,
label,
options,
formState,
disabled = false,
required = false,
}) => (
<AutocompleteInternal
disablePortal
options={options}
defaultValue={formState.values[name]}
fullWidth
size="small"
renderInput={(params) => (
<TextField
{...params}
label={label}
disabled={disabled}
required={required}
sx={{ backgroundColor: colors.white }}
/>
)}
/>
);

View file

@ -10,6 +10,7 @@ interface ListProps {
rows: any;
columns: GridColDef<any>[];
onRowClick?: (id: string) => void;
getRowID?: (row: any) => any;
buttons?: React.ReactNode;
paginate?: boolean;
}
@ -19,12 +20,20 @@ export const List: FC<ListProps> = ({
rows,
columns,
onRowClick,
getRowID,
buttons,
paginate = false,
}) => {
const { h3 } = typography;
const { mediumGray, lightGray, veryLightGray, mediumBlue, white, darkGray } =
colors;
const getRowIDInternal = (row: any) => {
if (getRowID) {
return getRowID(row);
}
return row.id;
};
return (
<Box sx={{ height: "100vh", backgroundColor: lightGray, p: 3 }}>
@ -92,7 +101,7 @@ export const List: FC<ListProps> = ({
scrollbarSize={0}
disableVirtualization
disableColumnMenu
onRowClick={(row: any) => onRowClick?.(row.id)}
onRowClick={({ row }: any) => onRowClick?.(getRowIDInternal(row))}
/>
</Box>
</Grid>

View file

@ -28,7 +28,7 @@ import { fonts } from "../styles/theme";
// import { useSession, signOut } from "next-auth/react";
const openWidth = 270;
const closedWidth = 100;
const closedWidth = 70;
const MenuItem = ({
name,

View file

@ -14,7 +14,7 @@ import Image from "next/image";
import { fonts } from "../styles/theme";
const openWidth = 270;
const closedWidth = 100;
const closedWidth = 70;
export const SidebarItem: FC = ({
name,

View file

@ -5,7 +5,7 @@ import {
IconButton,
} from "@mui/material";
import { Refresh as RefreshIcon } from "@mui/icons-material";
import { colors } from "../styles/theme";
import { colors, fonts } from "../styles/theme";
type TextFieldProps = {
name: string;
@ -28,7 +28,8 @@ export const TextField: FC<TextFieldProps> = ({
lines = 1,
helperText,
}) => {
const { darkMediumGray } = colors;
const { darkMediumGray, white } = colors;
const { roboto } = fonts;
return (
<InternalTextField
@ -58,7 +59,8 @@ export const TextField: FC<TextFieldProps> = ({
) : null,
sx: {
backgroundColor: "#fff",
fontFamily: roboto.style.fontFamily,
backgroundColor: white,
},
}}
/>

View file

@ -9,6 +9,7 @@ export { Button } from "./components/Button";
export { TextField } from "./components/TextField";
export { DisplayTextField } from "./components/DisplayTextField";
export { Select } from "./components/Select";
export { Autocomplete } from "./components/Autocomplete";
export { MultiValueField } from "./components/MultiValueField";
export { Dialog } from "./components/Dialog";
export { fonts, typography, colors } from "./styles/theme";

File diff suppressed because one or more lines are too long