This commit is contained in:
Darren Clarke 2023-08-25 07:11:33 +00:00
parent 8f165d15d2
commit c620e4bf25
264 changed files with 9983 additions and 2280 deletions

View file

@ -1,4 +1,4 @@
import { About } from './_components/About';
import { About } from "leafcutter-common";
export default function Page() {
return <About />;

View file

@ -1,8 +1,10 @@
import { getTemplates } from "app/_lib/opensearch";
import { Create } from "./_components/Create";
import { Create } from "leafcutter-common";
export default async function Page() {
const templates = await getTemplates(100);
return <Create templates={templates} />;
}
export const dynamic = "force-dynamic";

View file

@ -1,4 +1,4 @@
import { FAQ } from "./_components/FAQ";
import { FAQ } from "leafcutter-common";
export default function Page() {
return <FAQ />;

View file

@ -7,16 +7,12 @@ import "@fontsource/roboto/700.css";
import "@fontsource/playfair-display/900.css";
// import getConfig from "next/config";
// import { LicenseInfo } from "@mui/x-data-grid-pro";
import { InternalLayout } from "app/_components/InternalLayout";
import { headers } from 'next/headers'
import { InternalLayout } from "../_components/InternalLayout";
type LayoutProps = {
children: ReactNode;
};
export default function Layout({ children }: LayoutProps) {
const allHeaders = headers();
const embedded = Boolean(allHeaders.get('x-leafcutter-embedded'));
return <InternalLayout embedded={embedded}>{children}</InternalLayout>;
return <InternalLayout embedded={false}>{children}</InternalLayout>;
}

View file

@ -1,7 +1,7 @@
import { getServerSession } from "next-auth";
import { authOptions } from "app/_lib/auth";
import { getUserVisualizations } from "app/_lib/opensearch";
import { Home } from "app/_components/Home";
import { Home } from "leafcutter-common";
export default async function Page() {
const session = await getServerSession(authOptions);

View file

@ -1,10 +1,10 @@
/* eslint-disable no-underscore-dangle */
// import { Client } from "@opensearch-project/opensearch";
import { Preview } from "./_components/Preview";
import { Preview } from "leafcutter-common";
// import { createVisualization } from "lib/opensearch";
export default function Page() {
return <Preview visualization={undefined} visualizationType={""} data={[]}/>;
return <Preview visualization={undefined} visualizationType={""} data={[]} />;
}
/*

View file

@ -5,7 +5,7 @@ import { useLayoutEffect } from "react";
import { useRouter } from "next/navigation";
import { Grid, CircularProgress } from "@mui/material";
import Iframe from "react-iframe";
import { useAppContext } from "app/_components/AppProvider";
import { useAppContext } from "../../../_components/AppProvider";
export const Setup: FC = () => {
const {
@ -20,6 +20,7 @@ export const Setup: FC = () => {
<Grid
sx={{ width: "100%", height: 700 }}
direction="row"
container
justifyContent="space-around"
alignItems="center"
alignContent="center"

View file

@ -1,6 +1,5 @@
import { Setup } from './_components/Setup';
import { Setup } from "./_components/Setup";
export default function Page() {
return <Setup />;
}

View file

@ -1,8 +1,10 @@
import { getTrends } from "app/_lib/opensearch";
import { Trends } from "./_components/Trends";
import { Trends } from "../../../../../packages/leafcutter-common/components/Trends";
export default async function Page() {
const visualizations = await getTrends(25);
return <Trends visualizations={visualizations} />;
}
export const dynamic = "force-dynamic";

View file

@ -1,6 +1,6 @@
/* eslint-disable no-underscore-dangle */
import { Client } from "@opensearch-project/opensearch";
import { VisualizationDetail } from "app/_components/VisualizationDetail";
import { VisualizationDetail } from "leafcutter-common";
const getVisualization = async (visualizationID: string) => {
const node = `https://${process.env.OPENSEARCH_USERNAME}:${process.env.OPENSEARCH_PASSWORD}@${process.env.OPENSEARCH_URL}`;
@ -18,7 +18,7 @@ const getVisualization = async (visualizationID: string) => {
const response = rawResponse.body;
const hits = response.hits.hits.filter(
(hit: any) => hit._id.split(":")[1] === visualizationID[0]
(hit: any) => hit._id.split(":")[1] === visualizationID[0],
);
const hit = hits[0];
const visualization = {

View file

@ -8,7 +8,7 @@ import {
useState,
PropsWithChildren,
} from "react";
import { colors, typography } from "app/_styles/theme";
import { colors, typography } from "leafcutter-common/styles/theme";
const basePath = process.env.GITLAB_CI
? "/link/link-stack/apps/leafcutter"

View file

@ -7,7 +7,7 @@ import CookieConsent from "react-cookie-consent";
import { useCookies } from "react-cookie";
import { TopNav } from "./TopNav";
import { Sidebar } from "./Sidebar";
import { GettingStartedDialog } from "./GettingStartedDialog";
import { GettingStartedDialog } from "leafcutter-common";
import { useAppContext } from "./AppProvider";
// import { Footer } from "./Footer";

View file

@ -8,10 +8,10 @@ import { CookiesProvider } from "react-cookie";
import { I18n } from "react-polyglot";
import { AdapterDateFns } from "@mui/x-date-pickers-pro/AdapterDateFns";
import { LocalizationProvider } from "@mui/x-date-pickers-pro";
import { AppProvider } from "app/_components/AppProvider";
import { AppProvider } from "./AppProvider";
import { NextAppDirEmotionCacheProvider } from "tss-react/next/appDir";
import en from "app/_locales/en.json";
import fr from "app/_locales/fr.json";
import en from "leafcutter-common/locales/en.json";
import fr from "leafcutter-common/locales/fr.json";
import "@fontsource/poppins/400.css";
import "@fontsource/poppins/700.css";
import "@fontsource/roboto/400.css";
@ -21,7 +21,7 @@ import "app/_styles/global.css";
import { LicenseInfo } from "@mui/x-date-pickers-pro";
LicenseInfo.setLicenseKey(
"7c9bf25d9e240f76e77cbf7d2ba58a23Tz02NjU4OCxFPTE3MTU4NjIzMzQ2ODgsUz1wcm8sTE09c3Vic2NyaXB0aW9uLEtWPTI="
"7c9bf25d9e240f76e77cbf7d2ba58a23Tz02NjU4OCxFPTE3MTU4NjIzMzQ2ODgsUz1wcm8sTE09c3Vic2NyaXB0aW9uLEtWPTI=",
);
const messages: any = { en, fr };

View file

@ -20,8 +20,8 @@ import {
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useTranslate } from "react-polyglot";
import { useAppContext } from "app/_components/AppProvider";
import { Tooltip } from "app/_components/Tooltip";
import { useAppContext } from "./AppProvider";
import { Tooltip } from "leafcutter-common";
// import { ArrowCircleRight as ArrowCircleRightIcon } from "@mui/icons-material";
const MenuItem = ({

View file

@ -6,9 +6,9 @@ import Image from "next/legacy/image";
import { AppBar, Grid, Box } from "@mui/material";
import { useTranslate } from "react-polyglot";
import LeafcutterLogo from "images/leafcutter-logo.png";
import { AccountButton } from "app/_components/AccountButton";
import { HelpButton } from "app/_components/HelpButton";
import { Tooltip } from "app/_components/Tooltip";
import { AccountButton } from "./AccountButton";
import { HelpButton } from "./HelpButton";
import { Tooltip } from "leafcutter-common";
import { useAppContext } from "./AppProvider";
// import { LanguageSelect } from "./LanguageSelect";
@ -43,50 +43,51 @@ export const TopNav: FC = () => {
wrap="nowrap"
spacing={4}
>
<Link href="/" passHref>
<Grid
item
container
direction="row"
justifyContent="flex-start"
spacing={1}
wrap="nowrap"
sx={{ cursor: "pointer" }}
>
<Grid item sx={{ pr: 1 }}>
<Image src={LeafcutterLogo} alt="" width={56} height={52} />
<Grid
item
container
direction="row"
justifyContent="flex-start"
alignItems="center"
alignContent="center"
spacing={1}
wrap="nowrap"
sx={{ cursor: "pointer" }}
>
<Grid item sx={{ pr: 1 }}>
<Image src={LeafcutterLogo} alt="" width={56} height={52} />
</Grid>
<Grid item container direction="column" alignContent="flex-start">
<Grid item sx={{ mt: -1 }}>
<Box
sx={{
...h5,
color: leafcutterElectricBlue,
p: 0,
m: 0,
pt: 1,
textAlign: "left",
}}
>
Leafcutter
</Box>
</Grid>
<Grid item container direction="column" alignContent="flex-start">
<Grid item>
<Box
sx={{
...h5,
color: leafcutterElectricBlue,
p: 0,
m: 0,
pt: 1,
textAlign: "left",
}}
>
Leafcutter
</Box>
</Grid>
<Grid item>
<Box
sx={{
...h6,
m: 0,
p: 0,
color: cdrLinkOrange,
textAlign: "left",
}}
>
A Project of Center for Digital Resilience
</Box>
</Grid>
<Grid item>
<Box
sx={{
...h6,
m: 0,
p: 0,
color: cdrLinkOrange,
textAlign: "left",
}}
>
A Project of Center for Digital Resilience
</Box>
</Grid>
</Grid>
</Link>
</Grid>
<Grid item>
<HelpButton />
</Grid>

View file

@ -1,8 +1,15 @@
import type { NextAuthOptions } from "next-auth";
import Google from "next-auth/providers/google";
import Apple from "next-auth/providers/apple";
import Credentials from "next-auth/providers/credentials";
import { checkAuth } from "./opensearch";
export const authOptions: NextAuthOptions = {
pages: {
signIn: "/login",
error: "/login",
signOut: "/logout",
},
providers: [
Google({
clientId: process.env.GOOGLE_CLIENT_ID ?? "",
@ -12,6 +19,62 @@ export const authOptions: NextAuthOptions = {
clientId: process.env.APPLE_CLIENT_ID ?? "",
clientSecret: process.env.APPLE_CLIENT_SECRET ?? "",
}),
Credentials({
name: "Link",
credentials: {
authToken: { label: "AuthToken", type: "text", },
},
async authorize(credentials, req) {
const { headers } = req;
console.log({ headers });
const leafcutterUser = headers?.["x-leafcutter-user"];
const authToken = credentials?.authToken;
if (!leafcutterUser || leafcutterUser.trim() === "") {
console.log("no leafcutter user");
return null;
}
console.log({ authToken });
return null;
/*
try {
// add role check
await checkAuth(username, password);
const user = {
id: leafcutterUser,
email: leafcutterUser
};
return user;
} catch (e) {
console.log({ e });
}
return null;
*/
}
})
],
secret: process.env.NEXTAUTH_SECRET,
/*
callbacks: {
signIn: async ({ user, account, profile }) => {
const roles: any = [];
return roles.includes("admin") || roles.includes("agent");
},
session: async ({ session, user, token }) => {
// @ts-ignore
session.user.roles = token.roles;
return session;
},
jwt: async ({ token, user, account, profile, trigger }) => {
if (user) {
token.roles = [];
}
return token;
}
},*/
};

View file

@ -23,6 +23,24 @@ const createClient = () => new Client({
},
});
const createUserClient = (username: string, password: string) => new Client({
node: baseURL,
auth: {
username,
password,
},
ssl: {
rejectUnauthorized: false,
},
});
export const checkAuth = async (username: string, password: string) => {
const client = createUserClient(username, password);
const res = await client.ping();
return res.statusCode === 200;
};
const getDocumentID = (doc: any) => doc._id.split(":")[1];
const getEmbedURL = (tenant: string, visualizationID: string) =>

View file

@ -3,3 +3,7 @@ body {
overscroll-behavior-y: none;
text-size-adjust: none;
}
a {
text-decoration: none;
}

View file

@ -0,0 +1,15 @@
import { NextRequest, NextResponse } from "next/server";
export const GET = async (req: NextRequest) => {
const validDomains = "localhost";
console.log({ req });
return NextResponse.json({ response: "ok" });
};
export const POST = async (req: NextRequest) => {
const validDomains = "localhost";
console.log({ req });
return NextResponse.json({ response: "ok" });
};

View file

@ -1,38 +0,0 @@
import { createProxyMiddleware } from "http-proxy-middleware";
import { NextApiRequest, NextApiResponse } from "next";
import { getToken } from "next-auth/jwt";
const withAuthInfo =
(handler: any) => async (req: NextApiRequest, res: NextApiResponse) => {
const session: any = await getToken({
req,
secret: process.env.NEXTAUTH_SECRET,
});
if (!session) {
return res.redirect("/login");
}
req.headers["x-proxy-user"] = session.email.toLowerCase();
req.headers["x-proxy-roles"] = "leafcutter_user";
const auth = `${session.email.toLowerCase()}:${process.env.OPENSEARCH_USER_PASSWORD}`;
const buff = Buffer.from(auth);
const base64data = buff.toString("base64");
req.headers.Authorization = `Basic ${base64data}`;
return handler(req, res);
};
const proxy = createProxyMiddleware({
target: process.env.OPENSEARCH_DASHBOARDS_URL,
changeOrigin: true,
xfwd: true,
});
export default withAuthInfo(proxy);
export const config = {
api: {
bodyParser: false,
externalResolver: true,
},
};

View file

@ -3,7 +3,9 @@ import { getTrends } from "app/_lib/opensearch";
export const GET = async () => {
const results = await getTrends(5);
console.log({ results });
NextResponse.json(results);
};
export const dynamic = 'force-dynamic';

View file

@ -0,0 +1,13 @@
import { NextRequest, NextResponse } from "next/server";
import { getServerSession } from "next-auth";
import { authOptions } from "app/_lib/auth";
import { getUserVisualizations } from "app/_lib/opensearch";
export const GET = async () => {
const session = await getServerSession(authOptions);
const { user: { email } }: any = session;
const visualizations = await getUserVisualizations(email, 20);
return NextResponse.json(visualizations);
};

View file

@ -8,7 +8,7 @@ import "@fontsource/roboto/700.css";
import "@fontsource/playfair-display/900.css";
// import getConfig from "next/config";
// import { LicenseInfo } from "@mui/x-data-grid-pro";
import { MultiProvider } from "app/_components/MultiProvider";
import { MultiProvider } from "./_components/MultiProvider";
export const metadata: Metadata = {
title: "Leafcutter",

View file

@ -2,5 +2,5 @@ apiVersion: v2
name: leafcutter
description: A Helm chart for Kubernetes
type: application
version: 0.1.54
appVersion: "0.1.54"
version: 0.2.0
appVersion: "0.2.0"

View file

@ -1,57 +1,6 @@
import { NextResponse } from "next/server";
import { withAuth, NextRequestWithAuth } from "next-auth/middleware";
import getConfig from "next/config";
const rewriteURL = (request: NextRequestWithAuth, originBaseURL: string, destinationBaseURL: string, headers: any = {}) => {
if (request.nextUrl.protocol.startsWith('ws')) {
return NextResponse.next();
}
if (request.nextUrl.pathname.includes('/_next/static/development/')) {
return NextResponse.next();
}
const destinationURL = request.url.replace(originBaseURL, destinationBaseURL);
console.log(`Rewriting ${request.url} to ${destinationURL}`);
const requestHeaders = new Headers(request.headers);
for (const [key, value] of Object.entries(headers)) {
// @ts-ignore
requestHeaders.set(key, value);
}
requestHeaders.delete('connection');
// console.log({ finalHeaders: requestHeaders });
return NextResponse.rewrite(new URL(destinationURL), { request: { headers: requestHeaders } });
};
const checkRewrites = async (request: NextRequestWithAuth) => {
console.log({ currentURL: request.nextUrl.href });
const leafcutterBaseURL = process.env.LEAFCUTTER_URL ?? "http://localhost:3000";
const opensearchDashboardsURL = process.env.OPENSEARCH_URL ?? "http://localhost:5602";
if (request.nextUrl.pathname.startsWith('/proxy/opensearch')) {
console.log('proxying to zammad');
const { token } = request.nextauth;
const auth = `${token?.email?.toLowerCase()}:${process.env.OPENSEARCH_USER_PASSWORD}`;
const buff = Buffer.from(auth);
const base64data = buff.toString("base64");
const headers = {
'X-Proxy-User': token?.email?.toLowerCase(),
"X-Proxy-Roles": "leafcutter_user",
"Authorization": `Basic ${base64data}`
};
console.log({ headers });
return rewriteURL(request, `${leafcutterBaseURL}/proxy/opensearch`, opensearchDashboardsURL, headers);
}
};
import { withAuth } from "next-auth/middleware";
export default withAuth(
checkRewrites,
{
pages: {
signIn: `/login`,
@ -60,25 +9,30 @@ export default withAuth(
authorized: ({ token, req }) => {
const {
url,
headers,
} = req;
// check login page
const parsedURL = new URL(url);
if (parsedURL.pathname.startsWith('/login')) {
console.log({ url });
console.log({ pathname: parsedURL.pathname });
console.log({ allowed: parsedURL.pathname.startsWith("/app") });
const allowed = parsedURL.pathname.startsWith('/login') || parsedURL.pathname.startsWith('/api' || parsedURL.pathname.startsWith("/app"));
if (allowed) {
return true;
}
// check session auth
const authorizedDomains = ["redaranj.com", "digiresilience.org"];
const userDomain = token?.email?.toLowerCase().split("@").pop() ?? "unauthorized.net";
if (authorizedDomains.includes(userDomain)) {
if (token?.email) {
return true;
}
return false;
},
}
}
);
export const config = {
matcher: [
'/((?!api|app|bootstrap|3961|ui|translations|internal|login|node_modules|_next/static|_next/image|favicon.ico).*)',
],
};

View file

@ -7,9 +7,16 @@ const ContentSecurityPolicy = `
`;
module.exports = {
publicRuntimeConfig: {
embedded: true
},/*
transpilePackages: ["leafcutter-common"],
rewrites: async () => ({
fallback: [
{
source: "/:path*",
destination: "/api/proxy/:path*",
},
],
}),
/*
basePath: "/proxy/leafcutter",
assetPrefix: "/proxy/leafcutter",
i18n: {
@ -17,25 +24,19 @@ module.exports = {
defaultLocale: "en",
},
*/
/* rewrites: async () => ({
fallback: [
{
source: "/:path*",
destination: "/api/proxy/:path*",
},
],
}) */
/*
async headers() {
return [
{
source: '/:path*',
headers: [
/*
{
key: 'Content-Security-Policy',
value: ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim()
},
*/
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload'
@ -52,4 +53,5 @@ module.exports = {
},
]
}
*/
};

View file

@ -6,7 +6,7 @@
"login": "aws sso login --sso-session cdr",
"kubeconfig": "aws eks update-kubeconfig --name cdr-leafcutter-dashboard-cluster --profile cdr-leafcutter-dashboard-production",
"fwd:opensearch": "kubectl port-forward opensearch-cluster-master-0 9200:9200 --namespace leafcutter",
"fwd:dashboards": "kubectl port-forward opensearch-dashboards-1-59854cdb9b-vgmtf 5602:5601 --namespace leafcutter",
"fwd:dashboards": "kubectl port-forward opensearch-dashboards-1-59854cdb9b-mx4qq 5602:5601 --namespace leafcutter",
"build": "next build",
"start": "next start",
"export": "next export",
@ -21,44 +21,46 @@
"@fontsource/poppins": "^5.0.8",
"@fontsource/roboto": "^5.0.8",
"@mui/icons-material": "^5",
"@mui/lab": "^5.0.0-alpha.138",
"@mui/lab": "^5.0.0-alpha.140",
"@mui/material": "^5",
"@mui/x-data-grid-pro": "^6.11.0",
"@mui/x-date-pickers-pro": "^6.11.0",
"@mui/x-data-grid-pro": "^6.11.1",
"@mui/x-date-pickers-pro": "^6.11.1",
"@opensearch-project/opensearch": "^2.3.1",
"cryptr": "^6.2.0",
"date-fns": "^2.30.0",
"http-proxy-middleware": "^2.0.6",
"leafcutter-common": "*",
"material-ui-popup-state": "^5.0.9",
"next": "13.4.13",
"next-auth": "^4.22.4",
"next": "13.4.7",
"next-auth": "^4.23.1",
"next-http-proxy-middleware": "^1.2.5",
"nodemailer": "^6.9.4",
"react": "18.2.0",
"react-cookie": "^4.1.1",
"react-cookie": "^5.0.0",
"react-cookie-consent": "^8.0.1",
"react-dom": "18.2.0",
"react-iframe": "^1.8.5",
"react-markdown": "^8.0.7",
"react-polyglot": "^0.7.2",
"sharp": "^0.32.4",
"swr": "^2.2.0",
"sharp": "^0.32.5",
"swr": "^2.2.1",
"tss-react": "^4.8.8",
"uuid": "^9.0.0"
},
"devDependencies": {
"@babel/core": "^7.22.9",
"@types/node": "^20.4.8",
"@types/react": "18.2.18",
"@babel/core": "^7.22.10",
"@types/node": "^20.5.0",
"@types/react": "18.2.20",
"@types/uuid": "^9.0.2",
"babel-loader": "^9.1.3",
"eslint": "^8.46.0",
"eslint": "^8.47.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-next": "^13.4.13",
"eslint-config-next": "^13.4.16",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-import": "^2.28.0",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-react": "^7.33.1",
"eslint-plugin-react": "^7.33.2",
"typescript": "5.1.6"
}
}

View file

@ -0,0 +1,66 @@
import { createProxyMiddleware } from "http-proxy-middleware";
import { NextApiRequest, NextApiResponse } from "next";
import { getToken } from "next-auth/jwt";
/*
if (validDomains.includes(domain)) {
res.headers.set("Access-Control-Allow-Origin", origin);
res.headers.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
res.headers.set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
}
*/
const withAuthInfo =
(handler: any) => async (req: NextApiRequest, res: NextApiResponse) => {
const session: any = await getToken({
req,
secret: process.env.NEXTAUTH_SECRET,
});
let email = session?.email?.toLowerCase();
const requestSignature = req.query.signature;
const url = new URL(req.headers.referer as string);
const referrerSignature = url.searchParams.get("signature");
console.log({ requestSignature, referrerSignature });
const isAppPath = !!req.url?.startsWith("/app");
const isResourcePath = !!req.url?.match(/\/(api|app|bootstrap|3961|ui|translations|internal|login|node_modules)/);
if (requestSignature && isAppPath) {
console.log("Has Signature");
}
if (referrerSignature && isResourcePath) {
console.log("Has Signature");
}
if (!email) {
return res.status(401).json({ error: "Not authorized" });
}
req.headers["x-proxy-user"] = email;
req.headers["x-proxy-roles"] = "leafcutter_user";
const auth = `${email}:${process.env.OPENSEARCH_USER_PASSWORD}`;
const buff = Buffer.from(auth);
const base64data = buff.toString("base64");
req.headers.Authorization = `Basic ${base64data}`;
return handler(req, res);
};
const proxy = createProxyMiddleware({
target: process.env.OPENSEARCH_DASHBOARDS_URL,
changeOrigin: true,
xfwd: true,
});
export default withAuthInfo(proxy);
export const config = {
api: {
bodyParser: false,
externalResolver: true,
},
};

View file

@ -24,6 +24,17 @@
}
]
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
"../../packages/leafcutter-common/components/Create.tsx",
"../../packages/leafcutter-common/components/About.tsx",
"../../packages/leafcutter-common/components/FAQ.tsx",
"../../packages/leafcutter-common/components/Trends.tsx",
"../../packages/leafcutter-common/components/Preview.tsx",
"app/(main)/setup/_components/Setup.tsx"
, "app/api/proxy/[[...path]]" ],
"exclude": ["node_modules", "babel__core"]
}

View file

@ -1,9 +1,24 @@
"use client";
import { FC } from "react";
import { Box, Grid, Container, IconButton } from "@mui/material";
import { Apple as AppleIcon, Google as GoogleIcon } from "@mui/icons-material";
import { FC, useState } from "react";
import {
Box,
Grid,
Container,
IconButton,
Typography,
TextField,
} from "@mui/material";
import {
Apple as AppleIcon,
Google as GoogleIcon,
Key as KeyIcon,
} from "@mui/icons-material";
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 { useSearchParams } from "next/navigation";
type LoginProps = {
session: any;
@ -14,62 +29,198 @@ export const Login: FC<LoginProps> = ({ session }) => {
typeof window !== "undefined" && window.location.origin
? window.location.origin
: "";
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const params = useSearchParams();
const error = params.get("error");
const { darkGray, cdrLinkOrange, white } = colors;
const buttonStyles = {
borderRadius: 500,
width: "100%",
fontSize: "16px",
fontWeight: "bold",
backgroundColor: white,
"&:hover": {
color: white,
backgroundColor: cdrLinkOrange,
},
};
const fieldStyles = {
"& label.Mui-focused": {
color: cdrLinkOrange,
},
"& .MuiInput-underline:after": {
borderBottomColor: cdrLinkOrange,
},
"& .MuiFilledInput-underline:after": {
borderBottomColor: cdrLinkOrange,
},
"& .MuiOutlinedInput-root": {
"&.Mui-focused fieldset": {
borderColor: cdrLinkOrange,
},
},
};
return (
<>
<Grid container direction="row-reverse" sx={{ p: 3 }}>
<Grid item />
</Grid>
<Container maxWidth="md" sx={{ mt: 3, mb: 20 }}>
<Box sx={{ backgroundColor: darkGray, height: "100vh" }}>
<Container maxWidth="md" sx={{ p: 10 }}>
<Grid container spacing={2} direction="column" alignItems="center">
<Grid item>
<Box sx={{ maxWidth: 200 }} />
</Grid>
<Grid item sx={{ textAlign: "center" }} />
<Grid item>
{!session ? (
<Grid
container
spacing={3}
direction="column"
alignItems="center"
sx={{ width: 450, mt: 1 }}
<Grid
item
container
direction="row"
justifyContent="center"
alignItems="center"
>
<Grid item>
<Box
sx={{
width: "70px",
height: "70px",
margin: "0 auto",
}}
>
<Grid item sx={{ width: "100%" }}>
<IconButton
sx={buttonStyles}
onClick={() =>
signIn("google", {
callbackUrl: `${origin}/setup`,
})
}
>
<GoogleIcon sx={{ mr: 1 }} />
Google
</IconButton>
<Image
src={LinkLogo}
alt="Link logo"
width={70}
height={70}
style={{
objectFit: "cover",
filter: "grayscale(100) brightness(100)",
}}
/>
</Box>
</Grid>
<Grid item>
<Typography
variant="h2"
sx={{
fontSize: 36,
color: "white",
fontWeight: 700,
mt: 1,
ml: 0.5,
fontFamily: "Poppins",
}}
>
CDR Link
</Typography>
</Grid>
</Grid>
<Grid item sx={{ width: "100%" }}>
{!session ? (
<Container
maxWidth="xs"
sx={{
p: 3,
mt: 3,
}}
>
<Grid
container
spacing={3}
direction="column"
alignItems="center"
>
{error ? (
<Grid item sx={{ width: "100%" }}>
<Box sx={{ backgroundColor: "red", p: 3 }}>
<Typography
variant="body1"
sx={{
fontSize: 18,
color: "white",
textAlign: "center",
}}
>
{`${error} error`}
</Typography>
</Box>
</Grid>
) : null}
<Grid item sx={{ width: "100%" }}>
<IconButton
sx={buttonStyles}
onClick={() =>
signIn("google", {
callbackUrl: `${origin}/setup`,
})
}
>
<GoogleIcon sx={{ mr: 1 }} />
Sign in with Google
</IconButton>
</Grid>
<Grid item sx={{ width: "100%" }}>
<IconButton
aria-label="Sign in with Apple"
sx={buttonStyles}
onClick={() =>
signIn("apple", {
callbackUrl: `${window.location.origin}/setup`,
})
}
>
<AppleIcon sx={{ mr: 1 }} />
Sign in with Apple
</IconButton>
</Grid>
<Grid>
<Typography
variant="body1"
sx={{
fontSize: 18,
color: white,
textAlign: "center",
mt: 3,
}}
>
or
</Typography>
</Grid>
<Grid item sx={{ width: "100%" }}>
<TextField
value={email}
onChange={(e) => setEmail(e.target.value)}
label="Email"
variant="filled"
size="small"
fullWidth
sx={{ ...fieldStyles, backgroundColor: white }}
/>
</Grid>
<Grid item sx={{ ...fieldStyles, width: "100%" }}>
<TextField
value={password}
onChange={(e) => setPassword(e.target.value)}
label="Password"
variant="filled"
size="small"
fullWidth
sx={{ backgroundColor: white }}
type="password"
/>
</Grid>
<Grid item sx={{ width: "100%" }}>
<IconButton
sx={buttonStyles}
onClick={() =>
signIn("credentials", {
email,
password,
callbackUrl: `${origin}/setup`,
})
}
>
<KeyIcon sx={{ mr: 1 }} />
Sign in with Zammad credentials
</IconButton>
</Grid>
</Grid>
{/*
<Grid item sx={{ width: "100%" }}>
<IconButton
sx={buttonStyles}
onClick={() =>
signIn("apple", {
callbackUrl: `${window.location.origin}/setup`,
})
}
>
<AppleIcon sx={{ mr: 1 }} />
</IconButton>
</Grid>*/}
<Grid item sx={{ mt: 2 }} />
</Grid>
</Container>
) : null}
{session ? (
<Box component="h4">
@ -79,6 +230,6 @@ export const Login: FC<LoginProps> = ({ session }) => {
</Grid>
</Grid>
</Container>
</>
</Box>
);
};

View file

@ -0,0 +1,14 @@
"use client";
import dynamic from "next/dynamic";
type ClientOnlyProps = { children: JSX.Element };
const ClientOnly = (props: ClientOnlyProps) => {
const { children } = props;
return children;
};
export default dynamic(() => Promise.resolve(ClientOnly), {
ssr: false,
});

View file

@ -17,12 +17,14 @@ import {
import {
FeaturedPlayList as FeaturedPlayListIcon,
Person as PersonIcon,
Analytics as AnalyticsIcon,
Insights as InsightsIcon,
Logout as LogoutIcon,
Cottage as CottageIcon,
Settings as SettingsIcon,
ExpandCircleDown as ExpandCircleDownIcon,
Dvr as DvrIcon,
Assessment as AssessmentIcon,
LibraryBooks as LibraryBooksIcon,
} from "@mui/icons-material";
import { usePathname } from "next/navigation";
import Link from "next/link";
@ -161,7 +163,10 @@ interface SidebarProps {
export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
const pathname = usePathname();
const { data: session } = useSession();
console.log({ session });
const username = session?.user?.name || "User";
// @ts-ignore
const roles = session?.user?.roles || [];
const { data: overviewData, error: overviewError }: any = useSWR(
{
document: getTicketOverviewCountsQuery,
@ -419,17 +424,25 @@ export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
<MenuItem
name="Knowledge Base"
href="/knowledge"
Icon={CottageIcon}
Icon={LibraryBooksIcon}
iconSize={20}
selected={pathname.endsWith("/knowledge")}
open={open}
/>
<MenuItem
name="Reporting"
href="/reporting"
Icon={AssessmentIcon}
iconSize={20}
selected={pathname.endsWith("/reporting")}
open={open}
/>
<MenuItem
name="Leafcutter"
href="/leafcutter"
Icon={AnalyticsIcon}
Icon={InsightsIcon}
iconSize={20}
selected={pathname.endsWith("/leafcutter")}
selected={false}
open={open}
/>
<Collapse
@ -471,7 +484,7 @@ export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
<MenuItem
name="About"
href="/leafcutter/about"
Icon={AnalyticsIcon}
Icon={InsightsIcon}
iconSize={0}
selected={pathname.endsWith("/leafcutter/about")}
open={open}
@ -486,49 +499,57 @@ export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
selected={pathname.endsWith("/profile")}
open={open}
/>
<MenuItem
name="Admin"
href="/admin/zammad"
Icon={SettingsIcon}
iconSize={20}
open={open}
/>
<Collapse
in={pathname.startsWith("/admin/")}
timeout="auto"
unmountOnExit
onClick={undefined}
>
<List component="div" disablePadding>
{roles.includes("admin") && (
<>
<MenuItem
name="Zammad Settings"
name="Admin"
href="/admin/zammad"
Icon={FeaturedPlayListIcon}
iconSize={0}
selected={pathname.endsWith("/admin/zammad")}
Icon={SettingsIcon}
iconSize={20}
open={open}
/>
<MenuItem
name="Metamigo"
href="/admin/metamigo"
Icon={FeaturedPlayListIcon}
iconSize={0}
selected={pathname.endsWith("/admin/metamigo")}
open={open}
/>
<MenuItem
name="Label Studio"
href="/admin/label-studio"
Icon={FeaturedPlayListIcon}
iconSize={0}
selected={pathname.endsWith("/admin/label-studio")}
open={open}
/>
</List>
</Collapse>
<Collapse
in={pathname.startsWith("/admin/")}
timeout="auto"
unmountOnExit
onClick={undefined}
>
<List component="div" disablePadding>
<MenuItem
name="Zammad Settings"
href="/admin/zammad"
Icon={FeaturedPlayListIcon}
iconSize={0}
selected={pathname.endsWith("/admin/zammad")}
open={open}
/>
{false && roles.includes("metamigo") && (
<MenuItem
name="Metamigo"
href="/admin/metamigo"
Icon={FeaturedPlayListIcon}
iconSize={0}
selected={pathname.endsWith("/admin/metamigo")}
open={open}
/>
)}
{roles.includes("label_studio") && (
<MenuItem
name="Label Studio"
href="/admin/label-studio"
Icon={FeaturedPlayListIcon}
iconSize={0}
selected={pathname.endsWith("/admin/label-studio")}
open={open}
/>
)}
</List>
</Collapse>
</>
)}
<MenuItem
name="Zammad Interface"
href="/proxy/zammad"
href="/zammad"
Icon={DvrIcon}
iconSize={20}
open={open}

View file

@ -15,8 +15,7 @@ export const ZammadWrapper: FC<ZammadWrapperProps> = ({
}) => {
const router = useRouter();
const [display, setDisplay] = useState("none");
const url = `/proxy/zammad${path}`;
console.log({ url });
const url = `/zammad${path}`;
return (
// @ts-ignore
@ -33,8 +32,7 @@ export const ZammadWrapper: FC<ZammadWrapperProps> = ({
linkElement.contentDocument &&
linkElement.contentDocument?.querySelector &&
linkElement.contentDocument.querySelector("#navigation") &&
linkElement.contentDocument.querySelector("body") &&
linkElement.contentDocument.querySelector(".sidebar")
linkElement.contentDocument.querySelector("body")
) {
// @ts-ignore
linkElement.contentDocument.querySelector("#navigation").style =
@ -43,7 +41,10 @@ export const ZammadWrapper: FC<ZammadWrapperProps> = ({
linkElement.contentDocument.querySelector("body").style =
"font-family: Arial";
if (hideSidebar) {
if (
hideSidebar &&
linkElement.contentDocument.querySelector(".sidebar")
) {
// @ts-ignore
linkElement.contentDocument.querySelector(".sidebar").style =
"display: none";

View file

@ -0,0 +1,16 @@
"use client";
import { FC } from "react";
import { ApolloProvider } from "@apollo/client";
import { apolloClient } from "../_lib/apollo-client";
import dynamic from "next/dynamic";
const MetamigoAdmin = dynamic(() => import("./MetamigoAdmin"), {
ssr: false,
});
export const Admin: FC = () => (
<ApolloProvider client={apolloClient}>
<MetamigoAdmin />
</ApolloProvider>
);

View file

@ -6,13 +6,11 @@ import { FC, useEffect, useState } from "react";
import { Admin, Resource } from "react-admin";
import { useApolloClient } from "@apollo/client";
import polyglotI18nProvider from "ra-i18n-polyglot";
import { ThemeProvider, createTheme } from "@mui/material";
import { ThemeProvider, createTheme } from "@mui/material/styles";
import { metamigoDataProvider } from "../_lib/dataprovider";
import { theme } from "./layout/themes";
import { Layout } from "./layout";
import englishMessages from "../_i18n/en";
import users from "./users";
import accounts from "./accounts";
import whatsappBots from "./whatsapp/bots";
import whatsappMessages from "./whatsapp/messages";
import whatsappAttachments from "./whatsapp/attachments";
@ -42,29 +40,25 @@ const MetamigoAdmin: FC = () => {
}, [client]);
return (
dataProvider && (
<ThemeProvider theme={muiTheme}>
<Admin
disableTelemetry
dataProvider={dataProvider}
layout={Layout}
i18nProvider={i18nProvider}
// @ts-ignore
loginPage={AdminLogin}
// @ts-ignore
authProvider={authProvider}
>
<Resource name="webhooks" {...webhooks} />
<Resource name="whatsappBots" {...whatsappBots} />
<Resource name="whatsappMessages" {...whatsappMessages} />
<Resource name="whatsappAttachments" {...whatsappAttachments} />
<Resource name="signalBots" {...signalBots} />
<Resource name="voiceProviders" {...voiceProviders} />
<Resource name="voiceLines" {...voiceLines} />
<Resource name="users" {...users} />
<Resource name="accounts" {...accounts} />
<Resource name="languages" />
</Admin>
</ThemeProvider>
<Admin
disableTelemetry
dataProvider={dataProvider}
layout={Layout}
i18nProvider={i18nProvider}
// @ts-ignore
loginPage={AdminLogin}
// @ts-ignore
authProvider={authProvider}
>
<Resource name="webhooks" {...webhooks} />
<Resource name="whatsappBots" {...whatsappBots} />
<Resource name="whatsappMessages" {...whatsappMessages} />
<Resource name="whatsappAttachments" {...whatsappAttachments} />
<Resource name="signalBots" {...signalBots} />
<Resource name="voiceProviders" {...voiceProviders} />
<Resource name="voiceLines" {...voiceLines} />
<Resource name="languages" />
</Admin>
)
);
};

View file

@ -1,30 +0,0 @@
"use client";
import { FC } from "react";
import { Grid } from "@mui/material";
import Iframe from "react-iframe";
type MetamigoWrapperProps = {
path: string;
};
export const MetamigoWrapper: FC<MetamigoWrapperProps> = ({ path }) => {
return (
<Grid
container
spacing={0}
sx={{ height: "100%", width: "100%" }}
direction="column"
>
<Grid item sx={{ height: "100vh", width: "100%" }}>
<Iframe
id="metamigo"
url="/proxy/metamigo"
width="100%"
height="100%"
frameBorder={0}
/>
</Grid>
</Grid>
);
};

View file

@ -1,7 +1,7 @@
"use client";
import { FC } from "react";
import { makeStyles } from "@mui/styles";
// import { makeStyles } from "@mui/styles";
import {
SimpleForm,
TextInput,
@ -15,6 +15,7 @@ import {
} from "react-admin";
import { useSession } from "next-auth/react";
/*
const useStyles = makeStyles((_theme: any) => ({
defaultToolbar: {
flex: 1,
@ -22,14 +23,14 @@ const useStyles = makeStyles((_theme: any) => ({
justifyContent: "space-between",
},
}));
*/
type AccountEditToolbarProps = {
record?: any;
};
const AccountEditToolbar: FC<AccountEditToolbarProps> = (props) => {
const { data: session } = useSession();
const classes = useStyles(props);
const classes: any = {}; // useStyles(props);
return (
<Toolbar className={classes.defaultToolbar} {...props}>
<DeleteButton disabled={session?.user?.email === props.record?.userId} />

View file

@ -4,8 +4,9 @@ import { forwardRef } from "react";
import { AppBar, UserMenu, MenuItemLink, useTranslate } from "react-admin";
import Typography from "@mui/material/Typography";
import SettingsIcon from "@mui/icons-material/Settings";
import { makeStyles } from "@mui/styles";
import { colors } from "app/_styles/theme";
// import { makeStyles } from "@mui/styles";
/*
const useStyles = makeStyles({
title: {
flex: 1,
@ -17,7 +18,7 @@ const useStyles = makeStyles({
flex: 1,
},
});
*/
// eslint-disable-next-line react/display-name
const ConfigurationMenu = forwardRef<any, any>((props, ref) => {
const translate = useTranslate();
@ -33,21 +34,16 @@ const ConfigurationMenu = forwardRef<any, any>((props, ref) => {
);
});
const CustomUserMenu = (props: any) => (
<UserMenu {...props}>
<ConfigurationMenu />
</UserMenu>
);
const CustomAppBar = (props: any) => {
const classes = useStyles();
const classes: any = {}; // useStyles();
const { darkLavender } = colors;
return (
<AppBar
{...props}
elevation={1}
userMenu={<CustomUserMenu />}
elevation={0}
userMenu={<div />}
position="sticky"
sx={{ mt: -1 }}
sx={{ mt: -1, backgroundColor: darkLavender }}
>
<Typography
variant="h6"

View file

@ -2,15 +2,11 @@
/* eslint-disable camelcase */
import { FC, useState } from "react";
// import { useSelector } from "react-redux";
import SecurityIcon from "@mui/icons-material/Security";
import VoiceIcon from "@mui/icons-material/PhoneInTalk";
import { Box } from "@mui/material";
import { useTheme } from "@mui/styles";
// import { useTheme } from "@mui/styles";
import useMediaQuery from "@mui/material/useMediaQuery";
import { useTranslate, MenuItemLink } from "react-admin";
import users from "../users";
import accounts from "../accounts";
import webhooks from "../webhooks";
import voiceLines from "../voice/voicelines";
import voiceProviders from "../voice/providers";
@ -26,11 +22,10 @@ export const Menu: FC = ({ onMenuClick, logout, dense = false }: any) => {
menuSecurity: false,
});
const translate = useTranslate();
const theme = useTheme();
const theme: any = {}; // useTheme();
// @ts-ignore
const isXSmall = useMediaQuery(theme.breakpoints.down("xs"));
const isXSmall = false; // useMediaQuery(theme?.breakpoints?.down("xs"));
const open = true; // useSelector((state: any) => state.admin.ui.sidebarOpen);
// useSelector((state: any) => state.theme); // force rerender on theme change
const handleToggle = (menu: MenuName) => {
setState((state: any) => ({ ...state, [menu]: !state[menu] }));
@ -97,35 +92,6 @@ export const Menu: FC = ({ onMenuClick, logout, dense = false }: any) => {
sidebarIsOpen={open}
dense={dense}
/>
<SubMenu
handleToggle={() => handleToggle("menuSecurity")}
isOpen={state.menuSecurity}
sidebarIsOpen={open}
name="pos.menu.security"
icon={<SecurityIcon />}
dense={dense}
>
<MenuItemLink
to={`/users`}
primaryText={translate(`resources.users.name`, {
smart_count: 2,
})}
leftIcon={<users.icon />}
onClick={onMenuClick}
sidebarIsOpen={open}
dense={dense}
/>
<MenuItemLink
to={`/accounts`}
primaryText={translate(`resources.accounts.name`, {
smart_count: 2,
})}
leftIcon={<accounts.icon />}
onClick={onMenuClick}
sidebarIsOpen={open}
dense={dense}
/>
</SubMenu>
{isXSmall && logout}
</Box>
);

View file

@ -8,9 +8,10 @@ import ListItemIcon from "@mui/material/ListItemIcon";
import Typography from "@mui/material/Typography";
import Collapse from "@mui/material/Collapse";
import Tooltip from "@mui/material/Tooltip";
import { makeStyles } from "@mui/styles";
// import { makeStyles } from "@mui/styles";
import { useTranslate } from "react-admin";
/*
const useStyles = makeStyles((theme: any) => ({
icon: { minWidth: theme.spacing(5) },
sidebarIsOpen: {
@ -26,7 +27,7 @@ const useStyles = makeStyles((theme: any) => ({
},
},
}));
*/
type SubMenuProps = PropsWithChildren<{
dense: boolean;
handleToggle: () => void;
@ -46,7 +47,7 @@ export const SubMenu: FC<SubMenuProps> = ({
dense,
}: any) => {
const translate = useTranslate();
const classes = useStyles();
const classes: any = {}; // = useStyles();
const header = (
// @ts-ignore

View file

@ -45,8 +45,11 @@ export const theme = {
},
},
typography: {
h6: { fontSize: 16, fontWeight: 600, color: "#1bb1bb" },
fontFamily: "Poppins, sans-serif",
fontWeight: 900,
h6: { fontSize: 16, fontWeight: 600, color: "red" },
},
/*
overrides: {
RaMenuItemLink: {
root: {
@ -96,4 +99,5 @@ export const theme = {
},
},
},
*/
};

View file

@ -231,7 +231,7 @@ const VerificationCaptcha = ({
.replace(/signalcaptcha:\/\//, "")
.replace("“", "")
.replace("”", "")
.trim()
.trim(),
);
else setCode(value);
};
@ -294,7 +294,7 @@ const VerificationCodeInput = ({
headers: {
"Content-Type": "application/json",
},
}
},
);
setSubmitting(false);
const responseBody = await response.json();
@ -302,12 +302,10 @@ const VerificationCodeInput = ({
if (response.status === 200) {
confirmVerification();
} else if (responseBody.message)
// @ts-expect-error
setSubmissionError(`Error: ${responseBody.message}`);
else {
setSubmissionError(
// @ts-expect-error
"There was an error, sorry about that. Please try again later or contact support."
"There was an error, sorry about that. Please try again later or contact support.",
);
}
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeStyles } from "@mui/styles";
// import { makeStyles } from "@mui/styles";
import {
SimpleForm,
TextInput,
@ -16,6 +16,7 @@ import {
import { useSession } from "next-auth/react";
import { UserRoleInput } from "./shared";
/*
const useStyles = makeStyles((_theme: any) => ({
defaultToolbar: {
flex: 1,
@ -23,14 +24,16 @@ const useStyles = makeStyles((_theme: any) => ({
justifyContent: "space-between",
},
}));
*/
const UserEditToolbar = (props: any) => {
const classes = useStyles();
const classes: any = {}; // = useStyles();
const redirect = useRedirect();
const record = useRecordContext();
const {session} = props;
const record = useRecordContext();
const { session } = props;
const shouldDisableDelete = !session || !session.user || session.user.id === record.id;
const shouldDisableDelete =
!session || !session.user || session.user.id === record.id;
return (
<Toolbar className={classes.defaultToolbar}>

View file

@ -6,7 +6,7 @@ import dynamic from "next/dynamic";
import MicIcon from "@mui/icons-material/Mic";
import StopIcon from "@mui/icons-material/Stop";
import Button from "@mui/material/Button";
import { useTheme } from "@mui/styles"; // makeStyles,
// import { useTheme } from "@mui/styles"; // makeStyles,
// import AudioPlayer from "material-ui-audio-player";
import { useStopwatch } from "react-timer-hook";
import style from "./MicInput.module.css";
@ -15,10 +15,10 @@ import style from "./MicInput.module.css";
const ReactMic = dynamic<any>(
() => {
throw new Error(
"MIC INPUT FEATURE IS DISABLED"
"MIC INPUT FEATURE IS DISABLED",
); /* return import("react-mic").then((mod) => mod.ReactMic); */
},
{ ssr: false }
{ ssr: false },
);
const blobToDataUri = (blob: Blob) => {
@ -54,7 +54,7 @@ const blobToResult = async (blob: Blob) => {
const MicInput = (props: any) => {
const { seconds, minutes, hours, start, reset, pause } = useStopwatch();
const theme = useTheme();
const theme: any = {}; // useTheme();
const {
field: { onChange }, // value
} = useInput(props);

View file

@ -21,7 +21,7 @@ type TTSProvider = (voice: any, language: any, prompt: any) => Promise<void>;
const tts = async (providerId: any): Promise<TTSProvider> => {
const r = await fetch(
`/api/v1/voice/twilio/text-to-speech-token/${providerId}`
`/api/v1/voice/twilio/text-to-speech-token/${providerId}`,
);
const { token } = await r.json();
const twilioClient = await import("twilio-client");
@ -97,7 +97,7 @@ export const PromptInput = (form: any, ...rest: any[]) => (
<TextInput
source="promptText"
multiline
options={{ fullWidth: true }}
// options={{ fullWidth: true }}
InputProps={{ endAdornment: <TextToSpeechButton form={form} /> }}
{...rest}
/>
@ -106,7 +106,6 @@ export const PromptInput = (form: any, ...rest: any[]) => (
const validateVoice = (_args: any, values: any) => {
if (!values.language) return "validation.language";
if (!values.voice) return "validation.voice";
// @ts-expect-error
const availableVoices = TwilioLanguages.voices[values.language];
const found =
availableVoices.filter((v: any) => v.id === values.voice).length === 1;
@ -116,7 +115,6 @@ const validateVoice = (_args: any, values: any) => {
};
export const VoiceInput = (form: any, ...rest: any[]) => {
// @ts-expect-error
const voice = TwilioLanguages.voices[form.formData.language] || [];
return (
// @ts-expect-error
@ -140,7 +138,7 @@ const getAvailableNumbers = async (providerId: string) => {
return availableNumbers;
} catch (error) {
console.error(
`Could not fetch available numbers for provider ${providerId} - ${error}`
`Could not fetch available numbers for provider ${providerId} - ${error}`,
);
return [];
}
@ -287,21 +285,19 @@ export const AsyncSelectInput = (choiceLoader: () => Promise<any[]>, label, sour
export const VoiceLineSelectInput = AsyncSelectInput(getVoiceLineChoices, "Voice Line", "backendId", "validation.noVoiceLines" )
*/
export const VoiceLineSelectInput = (source: string) => () =>
(
<ReferenceInput
label="Voice Line"
source={source}
reference="voiceLines"
validate={[required()]}
>
<SelectInput optionText="number" />
</ReferenceInput>
);
export const VoiceLineSelectInput = (source: string) => () => (
<ReferenceInput
label="Voice Line"
source={source}
reference="voiceLines"
validate={[required()]}
>
<SelectInput optionText="number" />
</ReferenceInput>
);
export const VoiceLineField = (source: string) => () =>
(
<ReferenceField label="Voice Line" source={source} reference="voiceLines">
<TextField source="number" />
</ReferenceField>
);
export const VoiceLineField = (source: string) => () => (
<ReferenceField label="Voice Line" source={source} reference="voiceLines">
<TextField source="number" />
</ReferenceField>
);

View file

@ -55,8 +55,7 @@ export const BackendTypeInput = (props: any) => (
export const BackendIdInput = (form: any, ...rest: any[]) => {
const Component = form.formData.backendType
? // @ts-expect-error
backendInputComponents[form.formData.backendType]
? backendInputComponents[form.formData.backendType]
: false;
return <>{Component && <Component form={form} {...rest} />}</>;
};
@ -65,8 +64,7 @@ export const BackendIdField = (form: any, ...rest: any[]) => {
console.log(form);
const Component = form.record.backendType
? // @ts-expect-error
backendFieldComponents[form.record.backendType]
? backendFieldComponents[form.record.backendType]
: false;
return <>{Component && <Component form={form} {...rest} />}</>;
};

View file

@ -24,7 +24,7 @@ const errorLink = onError(
);
export const apolloClient = new ApolloClient({
link: ApolloLink.from([errorLink, new HttpLink({ uri: "/graphql" })]),
link: ApolloLink.from([errorLink, new HttpLink({ uri: "/proxy/metamigo/graphql" })]),
cache: new InMemoryCache(),
/*
defaultOptions: {

Some files were not shown because too many files have changed in this diff Show more