More app directory refactoring
This commit is contained in:
parent
b312a8c862
commit
8bbeaa25cf
55 changed files with 903 additions and 899 deletions
|
|
@ -8,7 +8,7 @@ import {
|
||||||
useState,
|
useState,
|
||||||
PropsWithChildren,
|
PropsWithChildren,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { colors, typography } from "styles/theme";
|
import { colors, typography } from "@/app/_styles/theme";
|
||||||
|
|
||||||
const basePath = process.env.GITLAB_CI
|
const basePath = process.env.GITLAB_CI
|
||||||
? "/link/link-stack/apps/leafcutter"
|
? "/link/link-stack/apps/leafcutter"
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { FC, useState } from "react";
|
import { FC, useState } from "react";
|
||||||
import { Dialog, Box, Grid, Checkbox, IconButton } from "@mui/material";
|
import { Dialog, Box, Grid, Checkbox, IconButton } from "@mui/material";
|
||||||
import { Close as CloseIcon } from "@mui/icons-material";
|
import { Close as CloseIcon } from "@mui/icons-material";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter, usePathname, useSearchParams } from "next/navigation";
|
||||||
import { useTranslate } from "react-polyglot";
|
import { useTranslate } from "react-polyglot";
|
||||||
import { useAppContext } from "./AppProvider";
|
import { useAppContext } from "./AppProvider";
|
||||||
|
|
||||||
|
|
@ -62,9 +62,11 @@ export const GettingStartedDialog: FC = () => {
|
||||||
typography: { h4 },
|
typography: { h4 },
|
||||||
} = useAppContext();
|
} = useAppContext();
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const [completedItems, setCompletedItems] = useState([] as any[]);
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const open = router.query.tooltip?.toString() === "checklist";
|
const [completedItems, setCompletedItems] = useState([] as any[]);
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const pathname = usePathname();
|
||||||
|
const open = searchParams.get("tooltip")?.toString() === "checklist";
|
||||||
const toggleCompletedItem = (item: any) => {
|
const toggleCompletedItem = (item: any) => {
|
||||||
if (completedItems.includes(item)) {
|
if (completedItems.includes(item)) {
|
||||||
setCompletedItems(completedItems.filter((i) => i !== item));
|
setCompletedItems(completedItems.filter((i) => i !== item));
|
||||||
|
|
@ -92,7 +94,7 @@ export const GettingStartedDialog: FC = () => {
|
||||||
<Box sx={{ ...h4, mb: 3 }}>{t("getStartedChecklist")}</Box>
|
<Box sx={{ ...h4, mb: 3 }}>{t("getStartedChecklist")}</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<IconButton onClick={() => router.push(router.pathname)}>
|
<IconButton onClick={() => router.push(pathname)}>
|
||||||
<CloseIcon sx={{ color: almostBlack, fontSize: "18px" }} />
|
<CloseIcon sx={{ color: almostBlack, fontSize: "18px" }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,21 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { FC, useState } from "react";
|
import { FC, useState } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter, usePathname } from "next/navigation";
|
||||||
import { Button } from "@mui/material";
|
import { Button } from "@mui/material";
|
||||||
import { QuestionMark as QuestionMarkIcon } from "@mui/icons-material";
|
import { QuestionMark as QuestionMarkIcon } from "@mui/icons-material";
|
||||||
import { useAppContext } from "./AppProvider";
|
import { useAppContext } from "./AppProvider";
|
||||||
|
|
||||||
export const HelpButton: FC = () => {
|
export const HelpButton: FC = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const pathname = usePathname();
|
||||||
const [helpActive, setHelpActive] = useState(false);
|
const [helpActive, setHelpActive] = useState(false);
|
||||||
const {
|
const {
|
||||||
colors: { leafcutterElectricBlue },
|
colors: { leafcutterElectricBlue },
|
||||||
} = useAppContext();
|
} = useAppContext();
|
||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
if (helpActive) {
|
if (helpActive) {
|
||||||
router.push(router.pathname);
|
router.push(pathname);
|
||||||
} else {
|
} else {
|
||||||
router.push("/?tooltip=welcome");
|
router.push("/?tooltip=welcome");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, FC } from "react";
|
import { useEffect, FC } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter, usePathname } from "next/navigation";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import { Grid, Button } from "@mui/material";
|
import { Grid, Button } from "@mui/material";
|
||||||
|
|
@ -14,11 +14,11 @@ import { useAppContext } from "@/app/_components/AppProvider";
|
||||||
|
|
||||||
type HomeProps = {
|
type HomeProps = {
|
||||||
visualizations: any;
|
visualizations: any;
|
||||||
embedded: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Home: FC<HomeProps> = ({ visualizations, embedded }) => {
|
export const Home: FC<HomeProps> = ({ visualizations }) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const pathname = usePathname();
|
||||||
const cookieName = "homeIntroComplete";
|
const cookieName = "homeIntroComplete";
|
||||||
const [cookies, setCookie] = useCookies([cookieName]);
|
const [cookies, setCookie] = useCookies([cookieName]);
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
|
|
@ -31,7 +31,7 @@ export const Home: FC<HomeProps> = ({ visualizations, embedded }) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (homeIntroComplete === 0) {
|
if (homeIntroComplete === 0) {
|
||||||
setCookie(cookieName, `${1}`, { path: "/" });
|
setCookie(cookieName, `${1}`, { path: "/" });
|
||||||
router.push(`${router.pathname}?tooltip=welcome`);
|
router.push(`${pathname}?tooltip=welcome`);
|
||||||
}
|
}
|
||||||
}, [homeIntroComplete, router, setCookie]);
|
}, [homeIntroComplete, router, setCookie]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/navigation";
|
||||||
import { IconButton, Menu, MenuItem, Box } from "@mui/material";
|
import { IconButton, Menu, MenuItem, Box } from "@mui/material";
|
||||||
import { KeyboardArrowDown as KeyboardArrowDownIcon } from "@mui/icons-material";
|
import { KeyboardArrowDown as KeyboardArrowDownIcon } from "@mui/icons-material";
|
||||||
import {
|
import {
|
||||||
|
|
@ -17,6 +17,7 @@ export const LanguageSelect = () => {
|
||||||
} = useAppContext();
|
} = useAppContext();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const locales: any = { en: "English", fr: "Français" };
|
const locales: any = { en: "English", fr: "Français" };
|
||||||
|
const locale = "en";
|
||||||
const popupState = usePopupState({ variant: "popover", popupId: "language" });
|
const popupState = usePopupState({ variant: "popover", popupId: "language" });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -38,7 +39,7 @@ export const LanguageSelect = () => {
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{locales[router.locale as any] ?? locales.en}
|
{locales[locale as any] ?? locales.en}
|
||||||
<KeyboardArrowDownIcon />
|
<KeyboardArrowDownIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Menu {...bindMenu(popupState)}>
|
<Menu {...bindMenu(popupState)}>
|
||||||
|
|
@ -46,7 +47,7 @@ export const LanguageSelect = () => {
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key={locale}
|
key={locale}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
router.push(router.route, router.route, { locale });
|
// router.push(router.route, router.route, { locale });
|
||||||
popupState.close();
|
popupState.close();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
/* eslint-disable react/jsx-props-no-spreading */
|
/* eslint-disable react/jsx-props-no-spreading */
|
||||||
import { FC, PropsWithChildren } from "react";
|
import { FC, PropsWithChildren } from "react";
|
||||||
import { SessionProvider } from "next-auth/react";
|
import { SessionProvider } from "next-auth/react";
|
||||||
|
|
@ -5,19 +7,17 @@ import { CssBaseline } from "@mui/material";
|
||||||
import { CookiesProvider } from "react-cookie";
|
import { CookiesProvider } from "react-cookie";
|
||||||
import { I18n } from "react-polyglot";
|
import { I18n } from "react-polyglot";
|
||||||
import { AdapterDateFns } from "@mui/x-date-pickers-pro/AdapterDateFns";
|
import { AdapterDateFns } from "@mui/x-date-pickers-pro/AdapterDateFns";
|
||||||
("use client");
|
|
||||||
|
|
||||||
import { LocalizationProvider } from "@mui/x-date-pickers-pro";
|
import { LocalizationProvider } from "@mui/x-date-pickers-pro";
|
||||||
import { AppProvider } from "@/app/_components/AppProvider";
|
import { AppProvider } from "@/app/_components/AppProvider";
|
||||||
import { NextAppDirEmotionCacheProvider } from "tss-react/next/appDir";
|
import { NextAppDirEmotionCacheProvider } from "tss-react/next/appDir";
|
||||||
import en from "locales/en.json";
|
import en from "app/_locales/en.json";
|
||||||
import fr from "locales/fr.json";
|
import fr from "app/_locales/fr.json";
|
||||||
import "@fontsource/poppins/400.css";
|
import "@fontsource/poppins/400.css";
|
||||||
import "@fontsource/poppins/700.css";
|
import "@fontsource/poppins/700.css";
|
||||||
import "@fontsource/roboto/400.css";
|
import "@fontsource/roboto/400.css";
|
||||||
import "@fontsource/roboto/700.css";
|
import "@fontsource/roboto/700.css";
|
||||||
import "@fontsource/playfair-display/900.css";
|
import "@fontsource/playfair-display/900.css";
|
||||||
import "styles/global.css";
|
import "app/_styles/global.css";
|
||||||
import { LicenseInfo } from "@mui/x-data-grid-pro";
|
import { LicenseInfo } from "@mui/x-data-grid-pro";
|
||||||
|
|
||||||
LicenseInfo.setLicenseKey(process.env.MUI_LICENSE_KEY ?? "");
|
LicenseInfo.setLicenseKey(process.env.MUI_LICENSE_KEY ?? "");
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { FC, useState, useEffect } from "react";
|
||||||
import { Box, Grid } from "@mui/material";
|
import { Box, Grid } from "@mui/material";
|
||||||
import { useTranslate } from "react-polyglot";
|
import { useTranslate } from "react-polyglot";
|
||||||
import taxonomy from "app/_config/taxonomy.json";
|
import taxonomy from "app/_config/taxonomy.json";
|
||||||
import { colors } from "styles/theme";
|
import { colors } from "@/app/_styles/theme";
|
||||||
import { useAppContext } from "./AppProvider";
|
import { useAppContext } from "./AppProvider";
|
||||||
|
|
||||||
export const QueryText: FC = () => {
|
export const QueryText: FC = () => {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import {
|
||||||
Drawer,
|
Drawer,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { usePathname } from "next/navigation";
|
||||||
import { useTranslate } from "react-polyglot";
|
import { useTranslate } from "react-polyglot";
|
||||||
import { useAppContext } from "@/app/_components/AppProvider";
|
import { useAppContext } from "@/app/_components/AppProvider";
|
||||||
import { Tooltip } from "@/app/_components/Tooltip";
|
import { Tooltip } from "@/app/_components/Tooltip";
|
||||||
|
|
@ -101,8 +101,8 @@ interface SidebarProps {
|
||||||
|
|
||||||
export const Sidebar: FC<SidebarProps> = ({ open }) => {
|
export const Sidebar: FC<SidebarProps> = ({ open }) => {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const router = useRouter();
|
const pathname = usePathname();
|
||||||
const section = router.pathname.split("/")[1];
|
const section = pathname.split("/")[1];
|
||||||
const {
|
const {
|
||||||
colors: { white }, // leafcutterElectricBlue, leafcutterLightBlue,
|
colors: { white }, // leafcutterElectricBlue, leafcutterLightBlue,
|
||||||
} = useAppContext();
|
} = useAppContext();
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
/* eslint-disable react/require-default-props */
|
/* eslint-disable react/require-default-props */
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter, usePathname, useSearchParams } from "next/navigation";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Grid,
|
Grid,
|
||||||
|
|
@ -40,7 +40,9 @@ export const Tooltip: FC<TooltipProps> = ({
|
||||||
colors: { white, leafcutterElectricBlue, almostBlack },
|
colors: { white, leafcutterElectricBlue, almostBlack },
|
||||||
} = useAppContext();
|
} = useAppContext();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const activeTooltip = router.query.tooltip?.toString();
|
const pathname = usePathname();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const activeTooltip = searchParams.get('tooltip')?.toString();
|
||||||
const open = activeTooltip === tooltipID;
|
const open = activeTooltip === tooltipID;
|
||||||
const showNavigation = true;
|
const showNavigation = true;
|
||||||
|
|
||||||
|
|
@ -51,7 +53,7 @@ export const Tooltip: FC<TooltipProps> = ({
|
||||||
<Grid container direction="column">
|
<Grid container direction="column">
|
||||||
<Grid item container direction="row-reverse">
|
<Grid item container direction="row-reverse">
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<IconButton onClick={() => router.push(router.pathname)}>
|
<IconButton onClick={() => router.push(pathname)}>
|
||||||
<CloseIcon
|
<CloseIcon
|
||||||
sx={{
|
sx={{
|
||||||
color: leafcutterElectricBlue,
|
color: leafcutterElectricBlue,
|
||||||
|
|
@ -125,7 +127,7 @@ export const Tooltip: FC<TooltipProps> = ({
|
||||||
color: leafcutterElectricBlue,
|
color: leafcutterElectricBlue,
|
||||||
textTransform: "none",
|
textTransform: "none",
|
||||||
}}
|
}}
|
||||||
onClick={() => router.push(router.pathname)}
|
onClick={() => router.push(pathname)}
|
||||||
>
|
>
|
||||||
{t("done")}
|
{t("done")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,12 @@ import { useAppContext } from "./AppProvider";
|
||||||
export const Welcome = () => {
|
export const Welcome = () => {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
|
/*
|
||||||
const {
|
const {
|
||||||
user: { name },
|
user: { name },
|
||||||
} = session as any;
|
} = session as any;
|
||||||
|
*/
|
||||||
|
const name = "Test User";
|
||||||
const {
|
const {
|
||||||
colors: { white, leafcutterElectricBlue },
|
colors: { white, leafcutterElectricBlue },
|
||||||
typography: { h1, h4, p },
|
typography: { h1, h4, p },
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Box, Grid, Dialog, Button } from "@mui/material";
|
import { Box, Grid, Dialog, Button } from "@mui/material";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
// import { useSession } from "next-auth/react";
|
// import { useSession } from "next-auth/react";
|
||||||
// import { useTranslate } from "react-polyglot";
|
// import { useTranslate } from "react-polyglot";
|
||||||
import { useAppContext } from "./AppProvider";
|
import { useAppContext } from "./AppProvider";
|
||||||
|
|
@ -9,13 +9,14 @@ import { useAppContext } from "./AppProvider";
|
||||||
export const WelcomeDialog = () => {
|
export const WelcomeDialog = () => {
|
||||||
// const t = useTranslate();
|
// const t = useTranslate();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
// const { data: session } = useSession();
|
// const { data: session } = useSession();
|
||||||
// const { user } = session;
|
// const { user } = session;
|
||||||
const {
|
const {
|
||||||
colors: { white, leafcutterElectricBlue },
|
colors: { white, leafcutterElectricBlue },
|
||||||
typography: { h1, h6, p },
|
typography: { h1, h6, p },
|
||||||
} = useAppContext();
|
} = useAppContext();
|
||||||
const activeTooltip = router.query.tooltip?.toString();
|
const activeTooltip = searchParams.get('tooltip')?.toString();
|
||||||
const open = activeTooltip === "welcome";
|
const open = activeTooltip === "welcome";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
17
apps/leafcutter/app/_lib/auth.ts
Normal file
17
apps/leafcutter/app/_lib/auth.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import type { NextAuthOptions } from "next-auth";
|
||||||
|
import Google from "next-auth/providers/google";
|
||||||
|
import Apple from "next-auth/providers/apple";
|
||||||
|
|
||||||
|
export const authOptions: NextAuthOptions = {
|
||||||
|
providers: [
|
||||||
|
Google({
|
||||||
|
clientId: process.env.GOOGLE_CLIENT_ID ?? "",
|
||||||
|
clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? "",
|
||||||
|
}),
|
||||||
|
Apple({
|
||||||
|
clientId: process.env.APPLE_CLIENT_ID ?? "",
|
||||||
|
clientSecret: process.env.APPLE_CLIENT_SECRET ?? "",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
secret: process.env.NEXTAUTH_SECRET,
|
||||||
|
};
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import createCache from "@emotion/cache";
|
|
||||||
|
|
||||||
export default function createEmotionCache() {
|
|
||||||
return createCache({ key: "css" });
|
|
||||||
}
|
|
||||||
|
|
@ -8,10 +8,16 @@ const globalIndex = ".kibana_1";
|
||||||
const dataIndexName = "sample_tagged_tickets";
|
const dataIndexName = "sample_tagged_tickets";
|
||||||
const userMetadataIndexName = "user_metadata";
|
const userMetadataIndexName = "user_metadata";
|
||||||
|
|
||||||
const baseURL = `https://${process.env.OPENSEARCH_USERNAME}:${process.env.OPENSEARCH_PASSWORD}@${process.env.OPENSEARCH_URL}`;
|
// const baseURL = `https://${process.env.OPENSEARCH_USERNAME}:${process.env.OPENSEARCH_PASSWORD}@${process.env.OPENSEARCH_URL}`;
|
||||||
|
|
||||||
|
const baseURL = `https://localhost:9200`;
|
||||||
|
|
||||||
const createClient = () => new Client({
|
const createClient = () => new Client({
|
||||||
node: baseURL,
|
node: baseURL,
|
||||||
|
auth: {
|
||||||
|
username: process.env.OPENSEARCH_USERNAME!,
|
||||||
|
password: process.env.OPENSEARCH_PASSWORD!,
|
||||||
|
},
|
||||||
ssl: {
|
ssl: {
|
||||||
rejectUnauthorized: false,
|
rejectUnauthorized: false,
|
||||||
},
|
},
|
||||||
|
|
@ -532,6 +538,7 @@ export const getTrends = async (limit: number) => {
|
||||||
|
|
||||||
export const getTemplates = async (limit: number) => {
|
export const getTemplates = async (limit: number) => {
|
||||||
const client = createClient();
|
const client = createClient();
|
||||||
|
|
||||||
const query = {
|
const query = {
|
||||||
query: {
|
query: {
|
||||||
bool: {
|
bool: {
|
||||||
|
|
@ -546,11 +553,14 @@ export const getTemplates = async (limit: number) => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const rawResponse = await client.search({
|
const rawResponse = await client.search({
|
||||||
index: globalIndex,
|
index: globalIndex,
|
||||||
size: limit,
|
size: limit,
|
||||||
body: query,
|
body: query,
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = rawResponse.body;
|
const response = rawResponse.body;
|
||||||
const {
|
const {
|
||||||
hits: { hits },
|
hits: { hits },
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
import { GetServerSidePropsContext } from "next";
|
|
||||||
|
|
||||||
export const getEmbedded = (context: GetServerSidePropsContext) =>
|
|
||||||
context.req.headers["x-leafcutter-embedded"] === "true";
|
|
||||||
164
apps/leafcutter/app/about/_components/About.tsx
Normal file
164
apps/leafcutter/app/about/_components/About.tsx
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { FC } from "react";
|
||||||
|
import { useTranslate } from "react-polyglot";
|
||||||
|
import Image from "next/legacy/image";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { Grid, Container, Box, Button } from "@mui/material";
|
||||||
|
import { useAppContext } from "@/app/_components/AppProvider";
|
||||||
|
import { PageHeader } from "@/app/_components/PageHeader";
|
||||||
|
import { AboutFeature } from "@/app/_components/AboutFeature";
|
||||||
|
import { AboutBox } from "@/app/_components/AboutBox";
|
||||||
|
import AbstractDiagram from "images/abstract-diagram.png";
|
||||||
|
import AboutHeader from "images/about-header.png";
|
||||||
|
import Globe from "images/globe.png";
|
||||||
|
import Controls from "images/controls.png";
|
||||||
|
import CommunityBackground from "images/community-background.png";
|
||||||
|
import Bicycle from "images/bicycle.png";
|
||||||
|
|
||||||
|
export const About: FC = () => {
|
||||||
|
const t = useTranslate();
|
||||||
|
const {
|
||||||
|
colors: { white, leafcutterElectricBlue, cdrLinkOrange },
|
||||||
|
typography: { h1, h4, p },
|
||||||
|
} = useAppContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PageHeader
|
||||||
|
backgroundColor={leafcutterElectricBlue}
|
||||||
|
sx={{
|
||||||
|
backgroundImage: `url(${AboutHeader.src})`,
|
||||||
|
backgroundSize: "200px",
|
||||||
|
backgroundPosition: "bottom right",
|
||||||
|
backgroundRepeat: "no-repeat",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
direction="row"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Grid item xs={9}>
|
||||||
|
<Box component="h1" sx={h1}>
|
||||||
|
{t("aboutLeafcutterTitle")}
|
||||||
|
</Box>
|
||||||
|
<Box component="h4" sx={{ ...h4, mt: 1, mb: 1 }}>
|
||||||
|
{t("aboutLeafcutterDescription")}
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</PageHeader>
|
||||||
|
<Container maxWidth="lg">
|
||||||
|
<AboutFeature
|
||||||
|
title={t("whatIsLeafcutterTitle")}
|
||||||
|
description={t("whatIsLeafcutterDescription")}
|
||||||
|
direction="row"
|
||||||
|
image={AbstractDiagram}
|
||||||
|
showBackground={false}
|
||||||
|
textColumns={8}
|
||||||
|
/>
|
||||||
|
<AboutFeature
|
||||||
|
title={t("whatIsItForTitle")}
|
||||||
|
description={t("whatIsItForDescription")}
|
||||||
|
direction="row-reverse"
|
||||||
|
image={Controls}
|
||||||
|
showBackground
|
||||||
|
textColumns={8}
|
||||||
|
/>
|
||||||
|
<AboutFeature
|
||||||
|
title={t("whoCanUseItTitle")}
|
||||||
|
description={t("whoCanUseItDescription")}
|
||||||
|
direction="row"
|
||||||
|
image={Globe}
|
||||||
|
showBackground
|
||||||
|
textColumns={6}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
<AboutBox backgroundColor={cdrLinkOrange}>
|
||||||
|
<Box component="h4" sx={{ ...h4, mt: 0 }}>
|
||||||
|
{t("whereDataComesFromTitle")}
|
||||||
|
</Box>
|
||||||
|
{t("whereDataComesFromDescription")
|
||||||
|
.split("\n")
|
||||||
|
.map((line: string, i: number) => (
|
||||||
|
<Box component="p" key={i} sx={p}>
|
||||||
|
{line}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</AboutBox>
|
||||||
|
<AboutBox backgroundColor={leafcutterElectricBlue}>
|
||||||
|
<Box component="h4" sx={{ ...h4, mt: 0 }}>
|
||||||
|
{t("projectSupportTitle")}
|
||||||
|
</Box>
|
||||||
|
{t("projectSupportDescription")
|
||||||
|
.split("\n")
|
||||||
|
.map((line: string, i: number) => (
|
||||||
|
<Box component="p" key={i} sx={p}>
|
||||||
|
{line}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</AboutBox>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
backgroundImage: `url(${CommunityBackground.src})`,
|
||||||
|
backgroundSize: "90%",
|
||||||
|
backgroundRepeat: "no-repeat",
|
||||||
|
backgroundPosition: "center",
|
||||||
|
position: "relative",
|
||||||
|
height: "700px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ position: "absolute", left: 0, bottom: -20, width: 300 }}>
|
||||||
|
<Image src={Bicycle} alt="" />
|
||||||
|
</Box>
|
||||||
|
<Container
|
||||||
|
maxWidth="md"
|
||||||
|
sx={{ textAlign: "center", paddingTop: "280px" }}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
component="h4"
|
||||||
|
sx={{ ...h4, maxWidth: 500, margin: "0 auto", mt: 3 }}
|
||||||
|
>
|
||||||
|
{t("interestedInLeafcutterTitle")}
|
||||||
|
</Box>
|
||||||
|
{t("interestedInLeafcutterDescription")
|
||||||
|
.split("\n")
|
||||||
|
.map((line: string, i: number) => (
|
||||||
|
<Box
|
||||||
|
component="p"
|
||||||
|
key={i}
|
||||||
|
sx={{ ...p, maxWidth: 500, margin: "0 auto" }}
|
||||||
|
>
|
||||||
|
{line}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
<Link href="mailto:info@digiresilience.org" passHref>
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
fontSize: 14,
|
||||||
|
borderRadius: 500,
|
||||||
|
color: white,
|
||||||
|
backgroundColor: cdrLinkOrange,
|
||||||
|
fontWeight: "bold",
|
||||||
|
textTransform: "uppercase",
|
||||||
|
pl: 6,
|
||||||
|
pr: 5,
|
||||||
|
mt: 4,
|
||||||
|
":hover": {
|
||||||
|
backgroundColor: leafcutterElectricBlue,
|
||||||
|
color: white,
|
||||||
|
opacity: 0.8,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("contactUs")}
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</Container>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
@ -1,173 +1,5 @@
|
||||||
import { NextPage, GetServerSideProps, GetServerSidePropsContext } from "next";
|
import { About } from './_components/About';
|
||||||
import { useTranslate } from "react-polyglot";
|
|
||||||
import Head from "next/head";
|
|
||||||
import Image from "next/legacy/image";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { Grid, Container, Box, Button } from "@mui/material";
|
|
||||||
import { useAppContext } from "@/app/_components/AppProvider";
|
|
||||||
import { PageHeader } from "@/app/_components/PageHeader";
|
|
||||||
import { AboutFeature } from "@/app/_components/AboutFeature";
|
|
||||||
import { AboutBox } from "@/app/_components/AboutBox";
|
|
||||||
import AbstractDiagram from "images/abstract-diagram.png";
|
|
||||||
import AboutHeader from "images/about-header.png";
|
|
||||||
import Globe from "images/globe.png";
|
|
||||||
import Controls from "images/controls.png";
|
|
||||||
import CommunityBackground from "images/community-background.png";
|
|
||||||
import Bicycle from "images/bicycle.png";
|
|
||||||
import { getEmbedded } from "@/app/_lib/utils";
|
|
||||||
|
|
||||||
type AboutProps = {
|
export default function Page() {
|
||||||
embedded: boolean;
|
return <About />;
|
||||||
};
|
}
|
||||||
|
|
||||||
const About: NextPage<AboutProps> = ({ embedded }) => {
|
|
||||||
const t = useTranslate();
|
|
||||||
const {
|
|
||||||
colors: { white, leafcutterElectricBlue, cdrLinkOrange },
|
|
||||||
typography: { h1, h4, p },
|
|
||||||
} = useAppContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<PageHeader
|
|
||||||
backgroundColor={leafcutterElectricBlue}
|
|
||||||
sx={{
|
|
||||||
backgroundImage: `url(${AboutHeader.src})`,
|
|
||||||
backgroundSize: "200px",
|
|
||||||
backgroundPosition: "bottom right",
|
|
||||||
backgroundRepeat: "no-repeat",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Grid
|
|
||||||
container
|
|
||||||
direction="row"
|
|
||||||
justifyContent="space-between"
|
|
||||||
alignItems="center"
|
|
||||||
>
|
|
||||||
<Grid item xs={9}>
|
|
||||||
<Box component="h1" sx={h1}>
|
|
||||||
{t("aboutLeafcutterTitle")}
|
|
||||||
</Box>
|
|
||||||
<Box component="h4" sx={{ ...h4, mt: 1, mb: 1 }}>
|
|
||||||
{t("aboutLeafcutterDescription")}
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</PageHeader>
|
|
||||||
<Container maxWidth="lg">
|
|
||||||
<AboutFeature
|
|
||||||
title={t("whatIsLeafcutterTitle")}
|
|
||||||
description={t("whatIsLeafcutterDescription")}
|
|
||||||
direction="row"
|
|
||||||
image={AbstractDiagram}
|
|
||||||
showBackground={false}
|
|
||||||
textColumns={8}
|
|
||||||
/>
|
|
||||||
<AboutFeature
|
|
||||||
title={t("whatIsItForTitle")}
|
|
||||||
description={t("whatIsItForDescription")}
|
|
||||||
direction="row-reverse"
|
|
||||||
image={Controls}
|
|
||||||
showBackground
|
|
||||||
textColumns={8}
|
|
||||||
/>
|
|
||||||
<AboutFeature
|
|
||||||
title={t("whoCanUseItTitle")}
|
|
||||||
description={t("whoCanUseItDescription")}
|
|
||||||
direction="row"
|
|
||||||
image={Globe}
|
|
||||||
showBackground
|
|
||||||
textColumns={6}
|
|
||||||
/>
|
|
||||||
</Container>
|
|
||||||
<AboutBox backgroundColor={cdrLinkOrange}>
|
|
||||||
<Box component="h4" sx={{ ...h4, mt: 0 }}>
|
|
||||||
{t("whereDataComesFromTitle")}
|
|
||||||
</Box>
|
|
||||||
{t("whereDataComesFromDescription")
|
|
||||||
.split("\n")
|
|
||||||
.map((line: string, i: number) => (
|
|
||||||
<Box component="p" key={i} sx={p}>
|
|
||||||
{line}
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
</AboutBox>
|
|
||||||
<AboutBox backgroundColor={leafcutterElectricBlue}>
|
|
||||||
<Box component="h4" sx={{ ...h4, mt: 0 }}>
|
|
||||||
{t("projectSupportTitle")}
|
|
||||||
</Box>
|
|
||||||
{t("projectSupportDescription")
|
|
||||||
.split("\n")
|
|
||||||
.map((line: string, i: number) => (
|
|
||||||
<Box component="p" key={i} sx={p}>
|
|
||||||
{line}
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
</AboutBox>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
backgroundImage: `url(${CommunityBackground.src})`,
|
|
||||||
backgroundSize: "90%",
|
|
||||||
backgroundRepeat: "no-repeat",
|
|
||||||
backgroundPosition: "center",
|
|
||||||
position: "relative",
|
|
||||||
height: "700px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box sx={{ position: "absolute", left: 0, bottom: -20, width: 300 }}>
|
|
||||||
<Image src={Bicycle} alt="" />
|
|
||||||
</Box>
|
|
||||||
<Container
|
|
||||||
maxWidth="md"
|
|
||||||
sx={{ textAlign: "center", paddingTop: "280px" }}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
component="h4"
|
|
||||||
sx={{ ...h4, maxWidth: 500, margin: "0 auto", mt: 3 }}
|
|
||||||
>
|
|
||||||
{t("interestedInLeafcutterTitle")}
|
|
||||||
</Box>
|
|
||||||
{t("interestedInLeafcutterDescription")
|
|
||||||
.split("\n")
|
|
||||||
.map((line: string, i: number) => (
|
|
||||||
<Box
|
|
||||||
component="p"
|
|
||||||
key={i}
|
|
||||||
sx={{ ...p, maxWidth: 500, margin: "0 auto" }}
|
|
||||||
>
|
|
||||||
{line}
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
<Link href="mailto:info@digiresilience.org" passHref>
|
|
||||||
<Button
|
|
||||||
sx={{
|
|
||||||
fontSize: 14,
|
|
||||||
borderRadius: 500,
|
|
||||||
color: white,
|
|
||||||
backgroundColor: cdrLinkOrange,
|
|
||||||
fontWeight: "bold",
|
|
||||||
textTransform: "uppercase",
|
|
||||||
pl: 6,
|
|
||||||
pr: 5,
|
|
||||||
mt: 4,
|
|
||||||
":hover": {
|
|
||||||
backgroundColor: leafcutterElectricBlue,
|
|
||||||
color: white,
|
|
||||||
opacity: 0.8,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("contactUs")}
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</Container>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default About;
|
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (
|
|
||||||
context: GetServerSidePropsContext
|
|
||||||
) => ({ props: { embedded: getEmbedded(context) } });
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,6 @@
|
||||||
import NextAuth from "next-auth";
|
import NextAuth from "next-auth";
|
||||||
import Google from "next-auth/providers/google";
|
import { authOptions } from "@/app/_lib/auth";
|
||||||
import Apple from "next-auth/providers/apple";
|
|
||||||
|
|
||||||
const handler = NextAuth({
|
const handler = NextAuth(authOptions);
|
||||||
providers: [
|
|
||||||
Google({
|
|
||||||
clientId: process.env.GOOGLE_CLIENT_ID ?? "",
|
|
||||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? "",
|
|
||||||
}),
|
|
||||||
Apple({
|
|
||||||
clientId: process.env.APPLE_CLIENT_ID ?? "",
|
|
||||||
clientSecret: process.env.APPLE_CLIENT_SECRET ?? "",
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
secret: process.env.NEXTAUTH_SECRET,
|
|
||||||
});
|
|
||||||
|
|
||||||
export { handler as GET, handler as POST };
|
export { handler as GET, handler as POST };
|
||||||
|
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
|
||||||
import { getToken } from "next-auth/jwt";
|
|
||||||
import { getUserMetadata, saveUserMetadata } from "@/app/_lib/opensearch";
|
|
||||||
|
|
||||||
export const POST = async (req: NextApiRequest, res: NextApiResponse) => {
|
|
||||||
const session = await getToken({
|
|
||||||
req,
|
|
||||||
secret: process.env.NEXTAUTH_SECRET,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
return res.redirect("/login");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.method !== "POST") {
|
|
||||||
return res.status(500).json({ message: "Only POST requests are allowed" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const { email }: any = session;
|
|
||||||
const { name, query } = JSON.parse(req.body);
|
|
||||||
const result = await getUserMetadata(email);
|
|
||||||
const { savedSearches } = result;
|
|
||||||
await saveUserMetadata(email, {
|
|
||||||
savedSearches: [...savedSearches, { name, query }]
|
|
||||||
});
|
|
||||||
const { savedSearches: updatedSavedSearches } = await getUserMetadata(email);
|
|
||||||
return res.json(updatedSavedSearches);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
22
apps/leafcutter/app/api/searches/create/route.ts
Normal file
22
apps/leafcutter/app/api/searches/create/route.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { getServerSession } from "next-auth";
|
||||||
|
import { authOptions } from "@/app/_lib/auth";
|
||||||
|
import { getUserMetadata, saveUserMetadata } from "@/app/_lib/opensearch";
|
||||||
|
|
||||||
|
export const POST = async (req: NextRequest) => {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
const { user: { email } }: any = session;
|
||||||
|
const { name, query } = await req.json();
|
||||||
|
const result = await getUserMetadata(email);
|
||||||
|
const { savedSearches } = result;
|
||||||
|
await saveUserMetadata(email, {
|
||||||
|
savedSearches: [...savedSearches, { name, query }]
|
||||||
|
});
|
||||||
|
const { savedSearches: updatedSavedSearches } = await getUserMetadata(email);
|
||||||
|
|
||||||
|
return NextResponse.json(updatedSavedSearches);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
|
||||||
import { getToken } from "next-auth/jwt";
|
|
||||||
import { getUserMetadata, saveUserMetadata } from "@/app/_lib/opensearch";
|
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|
||||||
const session = await getToken({
|
|
||||||
req,
|
|
||||||
secret: process.env.NEXTAUTH_SECRET,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
return res.redirect("/login");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.method !== "POST") {
|
|
||||||
return res.status(500).json({ message: "Only POST requests are allowed" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const { email }: any = session;
|
|
||||||
const { name } = JSON.parse(req.body);
|
|
||||||
const { savedSearches } = await getUserMetadata(email);
|
|
||||||
const updatedSavedSearches = savedSearches.filter((search: any) => search.name !== name);
|
|
||||||
const result = await saveUserMetadata(email, { savedSearches: updatedSavedSearches });
|
|
||||||
|
|
||||||
return res.json({ result });
|
|
||||||
};
|
|
||||||
|
|
||||||
export default handler;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
19
apps/leafcutter/app/api/searches/delete/route.ts
Normal file
19
apps/leafcutter/app/api/searches/delete/route.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { getServerSession } from "next-auth";
|
||||||
|
import { authOptions } from "@/app/_lib/auth";
|
||||||
|
import { getUserMetadata, saveUserMetadata } from "@/app/_lib/opensearch";
|
||||||
|
|
||||||
|
export const POST = async (req: NextRequest) => {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
const { user: { email } }: any = session;
|
||||||
|
const { name } = await req.json();
|
||||||
|
const { savedSearches } = await getUserMetadata(email);
|
||||||
|
const updatedSavedSearches = savedSearches.filter((search: any) => search.name !== name);
|
||||||
|
const result = await saveUserMetadata(email, { savedSearches: updatedSavedSearches });
|
||||||
|
|
||||||
|
return NextResponse.json({ result });
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
|
||||||
import { getToken } from "next-auth/jwt";
|
|
||||||
import { getUserMetadata } from "@/app/_lib/opensearch";
|
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|
||||||
const session = await getToken({
|
|
||||||
req,
|
|
||||||
secret: process.env.NEXTAUTH_SECRET,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
return res.redirect("/login");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.method !== "GET") {
|
|
||||||
return res.status(500).json({ message: "Only GET requests are allowed" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const { email }: any = session;
|
|
||||||
const { savedSearches } = await getUserMetadata(email);
|
|
||||||
|
|
||||||
return res.json(savedSearches);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default handler;
|
|
||||||
12
apps/leafcutter/app/api/searches/list/route.ts
Normal file
12
apps/leafcutter/app/api/searches/list/route.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { getServerSession } from "next-auth";
|
||||||
|
import { authOptions } from "@/app/_lib/auth";
|
||||||
|
import { getUserMetadata } from "@/app/_lib/opensearch";
|
||||||
|
|
||||||
|
export const GET = async () => {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
const { user: { email } }: any = session;
|
||||||
|
const { savedSearches } = await getUserMetadata(email);
|
||||||
|
|
||||||
|
return NextResponse.json(savedSearches);
|
||||||
|
};
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import { NextResponse } from "next/server";
|
|
||||||
// import { getToken } from "next-auth/jwt";
|
|
||||||
import { getTrends } from "@/app/_lib/opensearch";
|
|
||||||
|
|
||||||
export const GET = async () => {
|
|
||||||
/*
|
|
||||||
const session = await getToken({
|
|
||||||
req,
|
|
||||||
secret: process.env.NEXTAUTH_SECRET,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
return res.redirect("/login");
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
const results = await getTrends(5);
|
|
||||||
|
|
||||||
NextResponse.json(results);
|
|
||||||
};
|
|
||||||
|
|
||||||
9
apps/leafcutter/app/api/trends/recent/route.ts
Normal file
9
apps/leafcutter/app/api/trends/recent/route.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { getTrends } from "@/app/_lib/opensearch";
|
||||||
|
|
||||||
|
export const GET = async () => {
|
||||||
|
const results = await getTrends(5);
|
||||||
|
|
||||||
|
NextResponse.json(results);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
/* eslint-disable no-restricted-syntax */
|
/* eslint-disable no-restricted-syntax */
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { Client } from "@opensearch-project/opensearch";
|
import { Client } from "@opensearch-project/opensearch";
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
import taxonomy from "app/_config/taxonomy.json";
|
import taxonomy from "app/_config/taxonomy.json";
|
||||||
import unRegions from "app/_config/unRegions.json";
|
import unRegions from "app/_config/unRegions.json";
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
export const POST = async (req: NextRequest) => {
|
||||||
const { headers: { authorization }, body: { tickets } } = req;
|
const { tickets } = await req.json();
|
||||||
|
const authorization = req.headers.get("authorization");
|
||||||
const baseURL = `https://${process.env.OPENSEARCH_URL}`;
|
const baseURL = `https://${process.env.OPENSEARCH_URL}`;
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
node: baseURL,
|
node: baseURL,
|
||||||
|
|
@ -48,7 +49,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = { succeeded, failed };
|
const results = { succeeded, failed };
|
||||||
return res.json(results);
|
|
||||||
|
return NextResponse.json(results);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default handler;
|
|
||||||
|
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
|
||||||
import { getToken } from "next-auth/jwt";
|
|
||||||
import { createUserVisualization } from "@/app/_lib/opensearch";
|
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|
||||||
const session = await getToken({
|
|
||||||
req,
|
|
||||||
secret: process.env.NEXTAUTH_SECRET,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
return res.redirect("/login");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.method !== "POST") {
|
|
||||||
return res.status(500).json({ message: "Only POST requests are allowed" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const { visualizationID, title, description, query } = req.body;
|
|
||||||
const id = await createUserVisualization({
|
|
||||||
email: session.email as string,
|
|
||||||
visualizationID,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
query
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.json({ id });
|
|
||||||
};
|
|
||||||
|
|
||||||
export default handler;
|
|
||||||
|
|
||||||
20
apps/leafcutter/app/api/visualizations/create/route.ts
Normal file
20
apps/leafcutter/app/api/visualizations/create/route.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { getServerSession } from "next-auth";
|
||||||
|
import { authOptions } from "@/app/_lib/auth";
|
||||||
|
import { createUserVisualization } from "@/app/_lib/opensearch";
|
||||||
|
|
||||||
|
export const POSt = async (req: NextRequest) => {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
const { user: { email } }: any = session;
|
||||||
|
const { visualizationID, title, description, query } = await req.json();
|
||||||
|
const id = await createUserVisualization({
|
||||||
|
email,
|
||||||
|
visualizationID,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
query
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json({ id });
|
||||||
|
};
|
||||||
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
|
||||||
import { getToken } from "next-auth/jwt";
|
|
||||||
import { deleteUserVisualization } from "@/app/_lib/opensearch";
|
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|
||||||
const session = await getToken({
|
|
||||||
req,
|
|
||||||
secret: process.env.NEXTAUTH_SECRET,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
return res.redirect("/login");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.method !== "POST") {
|
|
||||||
return res.status(500).json({ message: "Only POST requests are allowed" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const { id } = req.body;
|
|
||||||
await deleteUserVisualization(session.email as string, id);
|
|
||||||
|
|
||||||
return res.json({ id });
|
|
||||||
};
|
|
||||||
|
|
||||||
export default handler;
|
|
||||||
|
|
||||||
15
apps/leafcutter/app/api/visualizations/delete/route.ts
Normal file
15
apps/leafcutter/app/api/visualizations/delete/route.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { getServerSession } from "next-auth";
|
||||||
|
import { authOptions } from "@/app/_lib/auth";
|
||||||
|
import { deleteUserVisualization } from "@/app/_lib/opensearch";
|
||||||
|
|
||||||
|
export const POST = async (req: NextRequest, res: NextResponse) => {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
const { user: { email } }: any = session;
|
||||||
|
const { id } = await req.json();
|
||||||
|
await deleteUserVisualization(email as string, id);
|
||||||
|
|
||||||
|
return NextResponse.json({ id });
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
|
||||||
import { getToken } from "next-auth/jwt";
|
|
||||||
import { performQuery } from "@/app/_lib/opensearch";
|
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|
||||||
const session = await getToken({
|
|
||||||
req,
|
|
||||||
secret: process.env.NEXTAUTH_SECRET,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
return res.redirect("/login");
|
|
||||||
}
|
|
||||||
|
|
||||||
const { searchQuery } = req.query;
|
|
||||||
const rawQuery = await JSON.parse(decodeURI(searchQuery as string));
|
|
||||||
const results = await performQuery(rawQuery, 1000);
|
|
||||||
|
|
||||||
return res.json(results);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default handler;
|
|
||||||
|
|
||||||
12
apps/leafcutter/app/api/visualizations/query/route.ts
Normal file
12
apps/leafcutter/app/api/visualizations/query/route.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { performQuery } from "@/app/_lib/opensearch";
|
||||||
|
|
||||||
|
export const GET = async (req: NextRequest) => {
|
||||||
|
const searchQuery = req.nextUrl.searchParams.get("searchQuery");
|
||||||
|
const rawQuery = await JSON.parse(decodeURI(searchQuery as string));
|
||||||
|
const results = await performQuery(rawQuery, 1000);
|
||||||
|
|
||||||
|
return NextResponse.json(results);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
|
||||||
import { getToken } from "next-auth/jwt";
|
|
||||||
import { updateUserVisualization } from "@/app/_lib/opensearch";
|
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|
||||||
const session = await getToken({
|
|
||||||
req,
|
|
||||||
secret: process.env.NEXTAUTH_SECRET,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
return res.redirect("/login");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.method !== "POST") {
|
|
||||||
return res.status(500).json({ message: "Only POST requests are allowed" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const { id, title, description, query } = req.body;
|
|
||||||
await updateUserVisualization({
|
|
||||||
email: session.email as string,
|
|
||||||
id,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
query
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.json({ id });
|
|
||||||
};
|
|
||||||
|
|
||||||
export default handler;
|
|
||||||
|
|
||||||
22
apps/leafcutter/app/api/visualizations/update/route.ts
Normal file
22
apps/leafcutter/app/api/visualizations/update/route.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { getServerSession } from "next-auth";
|
||||||
|
import { authOptions } from "@/app/_lib/auth";
|
||||||
|
import { updateUserVisualization } from "@/app/_lib/opensearch";
|
||||||
|
|
||||||
|
const handler = async (req: NextRequest) => {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
const { user: { email } }: any = session;
|
||||||
|
const { id, title, description, query } = await req.json();
|
||||||
|
await updateUserVisualization({
|
||||||
|
email,
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
query
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json({ id });
|
||||||
|
};
|
||||||
|
|
||||||
|
export default handler;
|
||||||
|
|
||||||
66
apps/leafcutter/app/create/_components/Create.tsx
Normal file
66
apps/leafcutter/app/create/_components/Create.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { FC, useEffect } from "react";
|
||||||
|
import { useTranslate } from "react-polyglot";
|
||||||
|
import { useRouter, usePathname } from "next/navigation";
|
||||||
|
import { Box, Grid } from "@mui/material";
|
||||||
|
import { useCookies } from "react-cookie";
|
||||||
|
import { useAppContext } from "@/app/_components/AppProvider";
|
||||||
|
import { PageHeader } from "@/app/_components/PageHeader";
|
||||||
|
import { VisualizationBuilder } from "@/app/_components/VisualizationBuilder";
|
||||||
|
|
||||||
|
type CreateProps = {
|
||||||
|
templates: any;
|
||||||
|
embedded: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Create: FC<CreateProps> = ({ templates, embedded }) => {
|
||||||
|
const t = useTranslate();
|
||||||
|
const {
|
||||||
|
colors: { cdrLinkOrange },
|
||||||
|
typography: { h1, h4 },
|
||||||
|
} = useAppContext();
|
||||||
|
const router = useRouter();
|
||||||
|
const pathname = usePathname();
|
||||||
|
const cookieName = "searchIntroComplete";
|
||||||
|
const [cookies, setCookie] = useCookies([cookieName]);
|
||||||
|
const searchIntroComplete = parseInt(cookies[cookieName], 10) || 0;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (searchIntroComplete === 0) {
|
||||||
|
setCookie(cookieName, `${1}`, { path: "/" });
|
||||||
|
router.push(`${pathname}?group=search&tooltip=1&checklist=1`);
|
||||||
|
}
|
||||||
|
}, [searchIntroComplete, router, setCookie]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PageHeader backgroundColor={cdrLinkOrange}>
|
||||||
|
<Grid container direction="row" spacing={2} alignItems="center">
|
||||||
|
{/* <Grid item xs={2} sx={{ textAlign: "center" }}>
|
||||||
|
<Image src={SearchCreateHeader} width={100} height={100} alt="" />
|
||||||
|
</Grid> */}
|
||||||
|
<Grid container direction="column" item xs={10}>
|
||||||
|
<Grid item>
|
||||||
|
<Box component="h1" sx={{ ...h1 }}>
|
||||||
|
{t("searchAndCreateTitle")}
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<Box component="h4" sx={{ ...h4, mt: 1, mb: 1 }}>
|
||||||
|
{t("searchAndCreateSubtitle")}
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
{/* <Grid>
|
||||||
|
<Box component="p" sx={{ ...p }}>
|
||||||
|
{t("searchAndCreateDescription")}
|
||||||
|
</Box>
|
||||||
|
</Grid> */}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</PageHeader>
|
||||||
|
<VisualizationBuilder templates={templates} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
@ -1,76 +1,9 @@
|
||||||
import { FC, useEffect } from "react";
|
|
||||||
import { GetServerSideProps, GetServerSidePropsContext } from "next";
|
|
||||||
import { useTranslate } from "react-polyglot";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { Box, Grid } from "@mui/material";
|
|
||||||
import { useCookies } from "react-cookie";
|
|
||||||
import { getTemplates } from "@/app/_lib/opensearch";
|
import { getTemplates } from "@/app/_lib/opensearch";
|
||||||
import { useAppContext } from "@/app/_components/AppProvider";
|
import { Create } from "./_components/Create";
|
||||||
import { PageHeader } from "@/app/_components/PageHeader";
|
|
||||||
import { VisualizationBuilder } from "@/app/_components/VisualizationBuilder";
|
|
||||||
import { getEmbedded } from "@/app/_lib/utils";
|
|
||||||
|
|
||||||
type CreateProps = {
|
export default async function Page() {
|
||||||
templates: any;
|
|
||||||
embedded: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Create: FC<CreateProps> = ({ templates, embedded }) => {
|
|
||||||
const t = useTranslate();
|
|
||||||
const {
|
|
||||||
colors: { cdrLinkOrange },
|
|
||||||
typography: { h1, h4 },
|
|
||||||
} = useAppContext();
|
|
||||||
const router = useRouter();
|
|
||||||
const cookieName = "searchIntroComplete";
|
|
||||||
const [cookies, setCookie] = useCookies([cookieName]);
|
|
||||||
const searchIntroComplete = parseInt(cookies[cookieName], 10) || 0;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (searchIntroComplete === 0) {
|
|
||||||
setCookie(cookieName, `${1}`, { path: "/" });
|
|
||||||
router.push(`${router.pathname}?group=search&tooltip=1&checklist=1`);
|
|
||||||
}
|
|
||||||
}, [searchIntroComplete, router, setCookie]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<PageHeader backgroundColor={cdrLinkOrange}>
|
|
||||||
<Grid container direction="row" spacing={2} alignItems="center">
|
|
||||||
{/* <Grid item xs={2} sx={{ textAlign: "center" }}>
|
|
||||||
<Image src={SearchCreateHeader} width={100} height={100} alt="" />
|
|
||||||
</Grid> */}
|
|
||||||
<Grid container direction="column" item xs={10}>
|
|
||||||
<Grid item>
|
|
||||||
<Box component="h1" sx={{ ...h1 }}>
|
|
||||||
{t("searchAndCreateTitle")}
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
<Grid item>
|
|
||||||
<Box component="h4" sx={{ ...h4, mt: 1, mb: 1 }}>
|
|
||||||
{t("searchAndCreateSubtitle")}
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
{/* <Grid>
|
|
||||||
<Box component="p" sx={{ ...p }}>
|
|
||||||
{t("searchAndCreateDescription")}
|
|
||||||
</Box>
|
|
||||||
</Grid> */}
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</PageHeader>
|
|
||||||
|
|
||||||
<VisualizationBuilder templates={templates} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Create;
|
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (
|
|
||||||
context: GetServerSidePropsContext
|
|
||||||
) => {
|
|
||||||
const templates = await getTemplates(100);
|
const templates = await getTemplates(100);
|
||||||
|
console.log({templates});
|
||||||
|
|
||||||
return { props: { templates, embedded: getEmbedded(context) } };
|
return <Create templates={templates} embedded={false}/>;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
102
apps/leafcutter/app/faq/_components/FAQ.tsx
Normal file
102
apps/leafcutter/app/faq/_components/FAQ.tsx
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { FC } from "react";
|
||||||
|
import { useTranslate } from "react-polyglot";
|
||||||
|
import { Box, Grid } from "@mui/material";
|
||||||
|
import { PageHeader } from "@/app/_components/PageHeader";
|
||||||
|
import { Question } from "@/app/_components/Question";
|
||||||
|
import { useAppContext } from "@/app/_components/AppProvider";
|
||||||
|
import FaqHeader from "images/faq-header.svg";
|
||||||
|
|
||||||
|
export const FAQ: FC = () => {
|
||||||
|
const t = useTranslate();
|
||||||
|
const {
|
||||||
|
colors: { lavender },
|
||||||
|
typography: { h1, h4, p },
|
||||||
|
} = useAppContext();
|
||||||
|
|
||||||
|
const questions = [
|
||||||
|
{
|
||||||
|
question: t("whatIsLeafcutterQuestion"),
|
||||||
|
answer: t("whatIsLeafcutterAnswer"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: t("whoBuiltLeafcutterQuestion"),
|
||||||
|
answer: t("whoBuiltLeafcutterAnswer"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: t("whoCanUseLeafcutterQuestion"),
|
||||||
|
answer: t("whoCanUseLeafcutterAnswer"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: t("whatCanYouDoWithLeafcutterQuestion"),
|
||||||
|
answer: t("whatCanYouDoWithLeafcutterAnswer"),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
question: t("whereIsTheDataComingFromQuestion"),
|
||||||
|
answer: t("whereIsTheDataComingFromAnswer"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: t("whereIsTheDataStoredQuestion"),
|
||||||
|
answer: t("whereIsTheDataStoredAnswer"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: t("howDoWeKeepTheDataSafeQuestion"),
|
||||||
|
answer: t("howDoWeKeepTheDataSafeAnswer"),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
question: t("howLongDoYouKeepTheDataQuestion"),
|
||||||
|
answer: t("howLongDoYouKeepTheDataAnswer"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: t("whatOrganizationsAreParticipatingQuestion"),
|
||||||
|
answer: t("whatOrganizationsAreParticipatingAnswer"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: t("howDidYouGetMyProfileInformationQuestion"),
|
||||||
|
answer: t("howDidYouGetMyProfileInformationAnswer"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: t("howCanILearnMoreAboutLeafcutterQuestion"),
|
||||||
|
answer: t("howCanILearnMoreAboutLeafcutterAnswer"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PageHeader
|
||||||
|
backgroundColor={lavender}
|
||||||
|
sx={{
|
||||||
|
backgroundImage: `url(${FaqHeader.src})`,
|
||||||
|
backgroundSize: "150px",
|
||||||
|
backgroundPosition: "bottom right",
|
||||||
|
backgroundRepeat: "no-repeat",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
direction="row"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Grid item>
|
||||||
|
<Box component="h1" sx={{ ...h1 }}>
|
||||||
|
{t("frequentlyAskedQuestionsTitle")}
|
||||||
|
</Box>
|
||||||
|
<Box component="h4" sx={{ ...h4, mt: 1, mb: 1 }}>
|
||||||
|
{t("frequentlyAskedQuestionsSubtitle")}
|
||||||
|
</Box>
|
||||||
|
<Box component="p" sx={{ ...p }}>
|
||||||
|
{t("frequentlyAskedQuestionsDescription")}
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</PageHeader>
|
||||||
|
{questions.map((q: any, index: number) => (
|
||||||
|
<Question key={index} question={q.question} answer={q.answer} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,111 +1,5 @@
|
||||||
import { useTranslate } from "react-polyglot";
|
import { FAQ } from "./_components/FAQ";
|
||||||
import { NextPage, GetServerSideProps, GetServerSidePropsContext } from "next";
|
|
||||||
import { Box, Grid } from "@mui/material";
|
|
||||||
import { PageHeader } from "@/app/_components/PageHeader";
|
|
||||||
import { Question } from "@/app/_components/Question";
|
|
||||||
import { useAppContext } from "@/app/_components/AppProvider";
|
|
||||||
import FaqHeader from "images/faq-header.svg";
|
|
||||||
import { getEmbedded } from "@/app/_lib/utils";
|
|
||||||
|
|
||||||
type FAQProps = {
|
export default function Page() {
|
||||||
embedded: boolean;
|
return <FAQ />;
|
||||||
};
|
}
|
||||||
|
|
||||||
const FAQ: NextPage<FAQProps> = ({ embedded }) => {
|
|
||||||
const t = useTranslate();
|
|
||||||
const {
|
|
||||||
colors: { lavender },
|
|
||||||
typography: { h1, h4, p },
|
|
||||||
} = useAppContext();
|
|
||||||
|
|
||||||
const questions = [
|
|
||||||
{
|
|
||||||
question: t("whatIsLeafcutterQuestion"),
|
|
||||||
answer: t("whatIsLeafcutterAnswer"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: t("whoBuiltLeafcutterQuestion"),
|
|
||||||
answer: t("whoBuiltLeafcutterAnswer"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: t("whoCanUseLeafcutterQuestion"),
|
|
||||||
answer: t("whoCanUseLeafcutterAnswer"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: t("whatCanYouDoWithLeafcutterQuestion"),
|
|
||||||
answer: t("whatCanYouDoWithLeafcutterAnswer"),
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
question: t("whereIsTheDataComingFromQuestion"),
|
|
||||||
answer: t("whereIsTheDataComingFromAnswer"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: t("whereIsTheDataStoredQuestion"),
|
|
||||||
answer: t("whereIsTheDataStoredAnswer"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: t("howDoWeKeepTheDataSafeQuestion"),
|
|
||||||
answer: t("howDoWeKeepTheDataSafeAnswer"),
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
question: t("howLongDoYouKeepTheDataQuestion"),
|
|
||||||
answer: t("howLongDoYouKeepTheDataAnswer"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: t("whatOrganizationsAreParticipatingQuestion"),
|
|
||||||
answer: t("whatOrganizationsAreParticipatingAnswer"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: t("howDidYouGetMyProfileInformationQuestion"),
|
|
||||||
answer: t("howDidYouGetMyProfileInformationAnswer"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: t("howCanILearnMoreAboutLeafcutterQuestion"),
|
|
||||||
answer: t("howCanILearnMoreAboutLeafcutterAnswer"),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<PageHeader
|
|
||||||
backgroundColor={lavender}
|
|
||||||
sx={{
|
|
||||||
backgroundImage: `url(${FaqHeader.src})`,
|
|
||||||
backgroundSize: "150px",
|
|
||||||
backgroundPosition: "bottom right",
|
|
||||||
backgroundRepeat: "no-repeat",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Grid
|
|
||||||
container
|
|
||||||
direction="row"
|
|
||||||
justifyContent="space-between"
|
|
||||||
alignItems="center"
|
|
||||||
>
|
|
||||||
<Grid item>
|
|
||||||
<Box component="h1" sx={{ ...h1 }}>
|
|
||||||
{t("frequentlyAskedQuestionsTitle")}
|
|
||||||
</Box>
|
|
||||||
<Box component="h4" sx={{ ...h4, mt: 1, mb: 1 }}>
|
|
||||||
{t("frequentlyAskedQuestionsSubtitle")}
|
|
||||||
</Box>
|
|
||||||
<Box component="p" sx={{ ...p }}>
|
|
||||||
{t("frequentlyAskedQuestionsDescription")}
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</PageHeader>
|
|
||||||
{questions.map((q: any, index: number) => (
|
|
||||||
<Question key={index} question={q.question} answer={q.answer} />
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default FAQ;
|
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (
|
|
||||||
context: GetServerSidePropsContext
|
|
||||||
) => ({ props: { embedded: getEmbedded(context) } });
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
import "./_styles/global.css";
|
import "app/_styles/global.css";
|
||||||
import "@fontsource/poppins/400.css";
|
import "@fontsource/poppins/400.css";
|
||||||
import "@fontsource/poppins/700.css";
|
import "@fontsource/poppins/700.css";
|
||||||
import "@fontsource/roboto/400.css";
|
import "@fontsource/roboto/400.css";
|
||||||
import "@fontsource/roboto/700.css";
|
import "@fontsource/roboto/700.css";
|
||||||
import "@fontsource/playfair-display/900.css";
|
import "@fontsource/playfair-display/900.css";
|
||||||
import "styles/global.css";
|
|
||||||
// import getConfig from "next/config";
|
// import getConfig from "next/config";
|
||||||
// import { LicenseInfo } from "@mui/x-data-grid-pro";
|
// import { LicenseInfo } from "@mui/x-data-grid-pro";
|
||||||
import { MultiProvider } from "app/_components/MultiProvider";
|
import { MultiProvider } from "app/_components/MultiProvider";
|
||||||
import { InternalLayout } from "app/_components/InternalLayout";
|
import { InternalLayout } from "app/_components/InternalLayout";
|
||||||
|
import { headers } from 'next/headers'
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Leafcutter",
|
title: "Leafcutter",
|
||||||
|
|
@ -21,6 +21,8 @@ type LayoutProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Layout({ children }: LayoutProps) {
|
export default function Layout({ children }: LayoutProps) {
|
||||||
|
const allHeaders = headers();
|
||||||
|
const embedded = Boolean(allHeaders.get('x-leafcutter-embedded'));
|
||||||
// const { publicRuntimeConfig } = getConfig();
|
// const { publicRuntimeConfig } = getConfig();
|
||||||
// LicenseInfo.setLicenseKey(publicRuntimeConfig.muiLicenseKey);
|
// LicenseInfo.setLicenseKey(publicRuntimeConfig.muiLicenseKey);
|
||||||
|
|
||||||
|
|
@ -28,7 +30,7 @@ export default function Layout({ children }: LayoutProps) {
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body>
|
<body>
|
||||||
<MultiProvider>
|
<MultiProvider>
|
||||||
<InternalLayout>{children}</InternalLayout>
|
<InternalLayout embedded={embedded}>{children}</InternalLayout>
|
||||||
</MultiProvider>
|
</MultiProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
114
apps/leafcutter/app/login/_components/Login.tsx
Normal file
114
apps/leafcutter/app/login/_components/Login.tsx
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { FC } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import Image from "next/legacy/image";
|
||||||
|
import { Box, Grid, Container, IconButton } from "@mui/material";
|
||||||
|
import { Apple as AppleIcon, Google as GoogleIcon } from "@mui/icons-material";
|
||||||
|
import { useTranslate } from "react-polyglot";
|
||||||
|
import { LanguageSelect } from "@/app/_components/LanguageSelect";
|
||||||
|
import LeafcutterLogoLarge from "images/leafcutter-logo-large.png";
|
||||||
|
import { signIn } from "next-auth/react";
|
||||||
|
import { useAppContext } from "@/app/_components/AppProvider";
|
||||||
|
|
||||||
|
type LoginProps = {
|
||||||
|
session: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Login: FC<LoginProps> = ({ session }) => {
|
||||||
|
const t = useTranslate();
|
||||||
|
const {
|
||||||
|
colors: { leafcutterElectricBlue, lightGray },
|
||||||
|
typography: { h1, h4 },
|
||||||
|
} = useAppContext();
|
||||||
|
const buttonStyles = {
|
||||||
|
backgroundColor: lightGray,
|
||||||
|
borderRadius: 500,
|
||||||
|
width: "100%",
|
||||||
|
fontSize: "16px",
|
||||||
|
fontWeight: "bold",
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Grid container direction="row-reverse" sx={{ p: 3 }}>
|
||||||
|
<Grid item>
|
||||||
|
<LanguageSelect />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Container maxWidth="md" sx={{ mt: 3, mb: 20 }}>
|
||||||
|
<Grid container spacing={2} direction="column" alignItems="center">
|
||||||
|
<Grid item>
|
||||||
|
<Box sx={{ maxWidth: 200 }}>
|
||||||
|
<Image src={LeafcutterLogoLarge} alt="" objectFit="fill" />
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ textAlign: "center" }}>
|
||||||
|
<Box component="h1" sx={{ ...h1, color: leafcutterElectricBlue }}>
|
||||||
|
{t("welcomeToLeafcutter")}
|
||||||
|
</Box>
|
||||||
|
<Box component="h4" sx={{ ...h4, mt: 1 }}>
|
||||||
|
{t("welcomeToLeafcutterDescription")}
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
{!session ? (
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
spacing={3}
|
||||||
|
direction="column"
|
||||||
|
alignItems="center"
|
||||||
|
sx={{ width: 450, mt: 1 }}
|
||||||
|
>
|
||||||
|
<Grid item sx={{ width: "100%" }}>
|
||||||
|
<IconButton
|
||||||
|
sx={buttonStyles}
|
||||||
|
onClick={() =>
|
||||||
|
signIn("google", {
|
||||||
|
callbackUrl: `${window.location.origin}/setup`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<GoogleIcon sx={{ mr: 1 }} />
|
||||||
|
{`${t("signInWith")} Google`}
|
||||||
|
</IconButton>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ width: "100%" }}>
|
||||||
|
<IconButton
|
||||||
|
sx={buttonStyles}
|
||||||
|
onClick={() =>
|
||||||
|
signIn("apple", {
|
||||||
|
callbackUrl: `${window.location.origin}/setup`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AppleIcon sx={{ mr: 1 }} />
|
||||||
|
{`${t("signInWith")} Apple`}
|
||||||
|
</IconButton>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ mt: 2 }}>
|
||||||
|
<Box>
|
||||||
|
{t("dontHaveAccount")}{" "}
|
||||||
|
<Link href="mailto:info@digiresilience.org">
|
||||||
|
{t("requestAccessHere")}
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
) : null}
|
||||||
|
{session ? (
|
||||||
|
<>
|
||||||
|
<Box component="h4" sx={h4}>
|
||||||
|
{`${t("welcome")}, ${
|
||||||
|
session.user.name ?? session.user.email
|
||||||
|
}.`}
|
||||||
|
</Box>
|
||||||
|
<Link href="/">{t("goHome")}</Link>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Container>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,126 +1,16 @@
|
||||||
import Head from "next/head";
|
import { Metadata } from "next";
|
||||||
import { NextPage } from "next";
|
import { getServerSession } from "next-auth";
|
||||||
import Link from "next/link";
|
import { authOptions } from "app/_lib/auth";
|
||||||
import Image from "next/legacy/image";
|
import { Login } from "./_components/Login";
|
||||||
import { Box, Grid, Container, IconButton } from "@mui/material";
|
|
||||||
import { Apple as AppleIcon, Google as GoogleIcon } from "@mui/icons-material";
|
|
||||||
import { useTranslate } from "react-polyglot";
|
|
||||||
import { LanguageSelect } from "@/app/_components/LanguageSelect";
|
|
||||||
import LeafcutterLogoLarge from "images/leafcutter-logo-large.png";
|
|
||||||
import { signIn, getSession } from "next-auth/react";
|
|
||||||
import { useAppContext } from "@/app/_components/AppProvider";
|
|
||||||
|
|
||||||
type LoginProps = {
|
export const metadata: Metadata = {
|
||||||
session: any;
|
title: "Login",
|
||||||
};
|
};
|
||||||
|
|
||||||
const Login: NextPage<LoginProps> = ({ session }) => {
|
export default async function Page() {
|
||||||
const t = useTranslate();
|
const session = await getServerSession(authOptions);
|
||||||
const {
|
|
||||||
colors: { leafcutterElectricBlue, lightGray },
|
|
||||||
typography: { h1, h4 },
|
|
||||||
} = useAppContext();
|
|
||||||
const buttonStyles = {
|
|
||||||
backgroundColor: lightGray,
|
|
||||||
borderRadius: 500,
|
|
||||||
width: "100%",
|
|
||||||
fontSize: "16px",
|
|
||||||
fontWeight: "bold",
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return <Login session={session} />;
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<title>Leafcutter: Login</title>
|
|
||||||
</Head>
|
|
||||||
<Grid container direction="row-reverse" sx={{ p: 3 }}>
|
|
||||||
<Grid item>
|
|
||||||
<LanguageSelect />
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
<Container maxWidth="md" sx={{ mt: 3, mb: 20 }}>
|
|
||||||
<Grid container spacing={2} direction="column" alignItems="center">
|
|
||||||
<Grid item>
|
|
||||||
<Box sx={{ maxWidth: 200 }}>
|
|
||||||
<Image src={LeafcutterLogoLarge} alt="" objectFit="fill" />
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
<Grid item sx={{ textAlign: "center" }}>
|
|
||||||
<Box component="h1" sx={{ ...h1, color: leafcutterElectricBlue }}>
|
|
||||||
{t("welcomeToLeafcutter")}
|
|
||||||
</Box>
|
|
||||||
<Box component="h4" sx={{ ...h4, mt: 1 }}>
|
|
||||||
{t("welcomeToLeafcutterDescription")}
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
<Grid item>
|
|
||||||
{!session ? (
|
|
||||||
<Grid
|
|
||||||
container
|
|
||||||
spacing={3}
|
|
||||||
direction="column"
|
|
||||||
alignItems="center"
|
|
||||||
sx={{ width: 450, mt: 1 }}
|
|
||||||
>
|
|
||||||
<Grid item sx={{ width: "100%" }}>
|
|
||||||
<IconButton
|
|
||||||
sx={buttonStyles}
|
|
||||||
onClick={() =>
|
|
||||||
signIn("google", {
|
|
||||||
callbackUrl: `${window.location.origin}/setup`,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<GoogleIcon sx={{ mr: 1 }} />
|
|
||||||
{`${t("signInWith")} Google`}
|
|
||||||
</IconButton>
|
|
||||||
</Grid>
|
|
||||||
<Grid item sx={{ width: "100%" }}>
|
|
||||||
<IconButton
|
|
||||||
sx={buttonStyles}
|
|
||||||
onClick={() =>
|
|
||||||
signIn("apple", {
|
|
||||||
callbackUrl: `${window.location.origin}/setup`,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<AppleIcon sx={{ mr: 1 }} />
|
|
||||||
{`${t("signInWith")} Apple`}
|
|
||||||
</IconButton>
|
|
||||||
</Grid>
|
|
||||||
<Grid item sx={{ mt: 2 }}>
|
|
||||||
<Box>
|
|
||||||
{t("dontHaveAccount")}{" "}
|
|
||||||
<Link href="mailto:info@digiresilience.org">
|
|
||||||
{t("requestAccessHere")}
|
|
||||||
</Link>
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
) : null}
|
|
||||||
{session ? (
|
|
||||||
<>
|
|
||||||
<Box component="h4" sx={h4}>
|
|
||||||
{`${t("welcome")}, ${
|
|
||||||
session.user.name ?? session.user.email
|
|
||||||
}.`}
|
|
||||||
</Box>
|
|
||||||
<Link href="/">{t("goHome")}</Link>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</Container>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Login;
|
|
||||||
|
|
||||||
export async function getServerSideProps(context: any) {
|
|
||||||
const session = (await getSession(context)) ?? null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: { session },
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { getSession } from "next-auth/react";
|
import { getSession } from "next-auth/react";
|
||||||
import { getUserVisualizations } from "@/app/_lib/opensearch";
|
import { getUserVisualizations } from "@/app/_lib/opensearch";
|
||||||
import { getEmbedded } from "@/app/_lib/utils";
|
|
||||||
import { Home } from "@/app/_components/Home";
|
import { Home } from "@/app/_components/Home";
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
|
|
@ -10,7 +9,6 @@ export default async function Page() {
|
||||||
session?.user?.email ?? "none",
|
session?.user?.email ?? "none",
|
||||||
20
|
20
|
||||||
);
|
);
|
||||||
const embedded = false; // getEmbedded(context);
|
|
||||||
|
|
||||||
return <Home visualizations={visualizations} embedded={embedded} />;
|
return <Home visualizations={visualizations} />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useLayoutEffect } from "react";
|
import { useLayoutEffect } from "react";
|
||||||
import { NextPage } from "next";
|
import { NextPage } from "next";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/navigation";
|
||||||
import { Grid, CircularProgress } from "@mui/material";
|
import { Grid, CircularProgress } from "@mui/material";
|
||||||
import Iframe from "react-iframe";
|
import Iframe from "react-iframe";
|
||||||
import { useAppContext } from "@/app/_components/AppProvider";
|
import { useAppContext } from "@/app/_components/AppProvider";
|
||||||
|
|
|
||||||
72
apps/leafcutter/app/trends/_components/Trends.tsx
Normal file
72
apps/leafcutter/app/trends/_components/Trends.tsx
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { FC, } from "react";
|
||||||
|
import { Grid, Box } from "@mui/material";
|
||||||
|
import { useTranslate } from "react-polyglot";
|
||||||
|
import { PageHeader } from "@/app/_components/PageHeader";
|
||||||
|
import { VisualizationCard } from "@/app/_components/VisualizationCard";
|
||||||
|
import { useAppContext } from "@/app/_components/AppProvider";
|
||||||
|
|
||||||
|
type TrendsProps = {
|
||||||
|
visualizations: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Trends: FC<TrendsProps> = ({ visualizations }) => {
|
||||||
|
const t = useTranslate();
|
||||||
|
const {
|
||||||
|
colors: { cdrLinkOrange },
|
||||||
|
typography: { h1, h4, p },
|
||||||
|
} = useAppContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PageHeader backgroundColor={cdrLinkOrange}>
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
direction="row"
|
||||||
|
spacing={2}
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
{/* <Grid item xs={3} sx={{ textAlign: "center" }}>
|
||||||
|
<Image src={SearchCreateHeader} width={200} height={200} alt="" />
|
||||||
|
</Grid> */}
|
||||||
|
<Grid item container direction="column" xs={12}>
|
||||||
|
<Grid item>
|
||||||
|
<Box component="h1" sx={{ ...h1 }}>
|
||||||
|
{t("trendsTitle")}
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<Box component="h4" sx={{ ...h4, mt: 1, mb: 1 }}>
|
||||||
|
{t("trendsSubtitle")}
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
<Box component="p" sx={{ ...p }}>
|
||||||
|
{t("trendsDescription")}
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</PageHeader>
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
direction="row"
|
||||||
|
wrap="wrap"
|
||||||
|
spacing={3}
|
||||||
|
justifyContent="space-between"
|
||||||
|
>
|
||||||
|
{visualizations.map((visualization: any, index: number) => (
|
||||||
|
<VisualizationCard
|
||||||
|
key={index}
|
||||||
|
id={visualization.id}
|
||||||
|
title={visualization.title}
|
||||||
|
description={visualization.description}
|
||||||
|
url={visualization.url}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,84 +1,8 @@
|
||||||
import { NextPage, GetServerSideProps, GetServerSidePropsContext } from "next";
|
|
||||||
import Head from "next/head";
|
|
||||||
import { Grid, Box } from "@mui/material";
|
|
||||||
import { useTranslate } from "react-polyglot";
|
|
||||||
import { getTrends } from "@/app/_lib/opensearch";
|
import { getTrends } from "@/app/_lib/opensearch";
|
||||||
import { PageHeader } from "@/app/_components/PageHeader";
|
import { Trends } from "./_components/Trends";
|
||||||
import { VisualizationCard } from "@/app/_components/VisualizationCard";
|
|
||||||
import { useAppContext } from "@/app/_components/AppProvider";
|
|
||||||
import { getEmbedded } from "@/app/_lib/utils";
|
|
||||||
|
|
||||||
type TrendsProps = {
|
export default async function Page() {
|
||||||
visualizations: any;
|
|
||||||
embedded: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Trends: NextPage<TrendsProps> = ({ visualizations, embedded }) => {
|
|
||||||
const t = useTranslate();
|
|
||||||
const {
|
|
||||||
colors: { cdrLinkOrange },
|
|
||||||
typography: { h1, h4, p },
|
|
||||||
} = useAppContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<PageHeader backgroundColor={cdrLinkOrange}>
|
|
||||||
<Grid
|
|
||||||
container
|
|
||||||
direction="row"
|
|
||||||
spacing={2}
|
|
||||||
justifyContent="space-between"
|
|
||||||
alignItems="center"
|
|
||||||
>
|
|
||||||
{/* <Grid item xs={3} sx={{ textAlign: "center" }}>
|
|
||||||
<Image src={SearchCreateHeader} width={200} height={200} alt="" />
|
|
||||||
</Grid> */}
|
|
||||||
<Grid item container direction="column" xs={12}>
|
|
||||||
<Grid item>
|
|
||||||
<Box component="h1" sx={{ ...h1 }}>
|
|
||||||
{t("trendsTitle")}
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
<Grid item>
|
|
||||||
<Box component="h4" sx={{ ...h4, mt: 1, mb: 1 }}>
|
|
||||||
{t("trendsSubtitle")}
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
<Grid>
|
|
||||||
<Box component="p" sx={{ ...p }}>
|
|
||||||
{t("trendsDescription")}
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</PageHeader>
|
|
||||||
<Grid
|
|
||||||
container
|
|
||||||
direction="row"
|
|
||||||
wrap="wrap"
|
|
||||||
spacing={3}
|
|
||||||
justifyContent="space-between"
|
|
||||||
>
|
|
||||||
{visualizations.map((visualization: any, index: number) => (
|
|
||||||
<VisualizationCard
|
|
||||||
key={index}
|
|
||||||
id={visualization.id}
|
|
||||||
title={visualization.title}
|
|
||||||
description={visualization.description}
|
|
||||||
url={visualization.url}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Trends;
|
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (
|
|
||||||
context: GetServerSidePropsContext
|
|
||||||
) => {
|
|
||||||
const visualizations = await getTrends(25);
|
const visualizations = await getTrends(25);
|
||||||
|
|
||||||
return { props: { visualizations, embedded: getEmbedded(context) } };
|
return <Trends visualizations={visualizations} />;
|
||||||
};
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,8 @@
|
||||||
/* eslint-disable no-underscore-dangle */
|
/* eslint-disable no-underscore-dangle */
|
||||||
import { NextPage, GetServerSideProps, GetServerSidePropsContext } from "next";
|
|
||||||
import { Client } from "@opensearch-project/opensearch";
|
import { Client } from "@opensearch-project/opensearch";
|
||||||
import { VisualizationDetail } from "@/app/_components/VisualizationDetail";
|
import { VisualizationDetail } from "@/app/_components/VisualizationDetail";
|
||||||
import { getEmbedded } from "@/app/_lib/utils";
|
|
||||||
|
|
||||||
type VisualizationProps = {
|
|
||||||
visualization: any;
|
|
||||||
embedded: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Visualization: NextPage<VisualizationProps> = ({
|
|
||||||
visualization,
|
|
||||||
embedded,
|
|
||||||
}) => <VisualizationDetail {...visualization} />;
|
|
||||||
|
|
||||||
export default Visualization;
|
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (
|
|
||||||
context: GetServerSidePropsContext
|
|
||||||
) => {
|
|
||||||
const { visualizationID } = context.query;
|
|
||||||
|
|
||||||
|
const getVisualization = async (visualizationID: string) => {
|
||||||
const node = `https://${process.env.OPENSEARCH_USERNAME}:${process.env.OPENSEARCH_PASSWORD}@${process.env.OPENSEARCH_URL}`;
|
const node = `https://${process.env.OPENSEARCH_USERNAME}:${process.env.OPENSEARCH_PASSWORD}@${process.env.OPENSEARCH_URL}`;
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
node,
|
node,
|
||||||
|
|
@ -36,7 +18,6 @@ export const getServerSideProps: GetServerSideProps = async (
|
||||||
const response = rawResponse.body;
|
const response = rawResponse.body;
|
||||||
|
|
||||||
const hits = response.hits.hits.filter(
|
const hits = response.hits.hits.filter(
|
||||||
// @ts-expect-error
|
|
||||||
(hit: any) => hit._id.split(":")[1] === visualizationID[0]
|
(hit: any) => hit._id.split(":")[1] === visualizationID[0]
|
||||||
);
|
);
|
||||||
const hit = hits[0];
|
const hit = hits[0];
|
||||||
|
|
@ -49,5 +30,19 @@ export const getServerSideProps: GetServerSideProps = async (
|
||||||
}?embed=true`,
|
}?embed=true`,
|
||||||
};
|
};
|
||||||
|
|
||||||
return { props: { visualization, embedded: getEmbedded(context) } };
|
return visualization;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
type PageProps = {
|
||||||
|
params: {
|
||||||
|
visualizationID: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function Page({ params: { visualizationID } }: PageProps) {
|
||||||
|
const visualization = await getVisualization(visualizationID);
|
||||||
|
|
||||||
|
return <VisualizationDetail {...visualization} editing={false}/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,57 @@
|
||||||
import { withAuth } from "next-auth/middleware";
|
import { NextResponse } from "next/server";
|
||||||
|
import { withAuth, NextRequestWithAuth } from "next-auth/middleware";
|
||||||
import getConfig from "next/config";
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default withAuth(
|
export default withAuth(
|
||||||
() => { },
|
checkRewrites,
|
||||||
{
|
{
|
||||||
pages: {
|
pages: {
|
||||||
signIn: `/login`,
|
signIn: `/login`,
|
||||||
|
|
@ -14,16 +63,6 @@ export default withAuth(
|
||||||
headers,
|
headers,
|
||||||
} = req;
|
} = req;
|
||||||
|
|
||||||
return true;
|
|
||||||
/*
|
|
||||||
const {
|
|
||||||
publicRuntimeConfig: { embedded },
|
|
||||||
} = getConfig();
|
|
||||||
|
|
||||||
if (embedded) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check login page
|
// check login page
|
||||||
const parsedURL = new URL(url);
|
const parsedURL = new URL(url);
|
||||||
if (parsedURL.pathname.startsWith('/login')) {
|
if (parsedURL.pathname.startsWith('/login')) {
|
||||||
|
|
@ -39,7 +78,6 @@ export default withAuth(
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
*/
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,22 @@
|
||||||
|
const ContentSecurityPolicy = `
|
||||||
|
default-src 'self';
|
||||||
|
script-src 'self';
|
||||||
|
child-src example.com;
|
||||||
|
style-src 'self' example.com;
|
||||||
|
font-src 'self';
|
||||||
|
`;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
publicRuntimeConfig: {
|
publicRuntimeConfig: {
|
||||||
embedded: true
|
embedded: true
|
||||||
},
|
},/*
|
||||||
basePath: "/proxy/leafcutter",
|
basePath: "/proxy/leafcutter",
|
||||||
assetPrefix: "/proxy/leafcutter",
|
assetPrefix: "/proxy/leafcutter",
|
||||||
i18n: {
|
i18n: {
|
||||||
locales: ["en", "fr"],
|
locales: ["en", "fr"],
|
||||||
defaultLocale: "en",
|
defaultLocale: "en",
|
||||||
},
|
},
|
||||||
|
*/
|
||||||
/* rewrites: async () => ({
|
/* rewrites: async () => ({
|
||||||
fallback: [
|
fallback: [
|
||||||
{
|
{
|
||||||
|
|
@ -17,4 +25,31 @@ module.exports = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}) */
|
}) */
|
||||||
|
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'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'X-XSS-Protection',
|
||||||
|
value: '1; mode=block'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'X-Frame-Options',
|
||||||
|
value: 'SAMEORIGIN'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 3001",
|
"dev": "next dev -p 3001",
|
||||||
"login": "aws sso login --profile cdr-leafcutter-dashboard-production",
|
"login": "aws sso login --sso-session cdr",
|
||||||
"kubeconfig": "aws eks update-kubeconfig --name cdr-leafcutter-dashboard-cluster --profile cdr-leafcutter-dashboard-production",
|
"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: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-vgmtf 5602:5601 --namespace leafcutter",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
|
@ -16,9 +20,24 @@
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./*", "../../node_modules/*"]
|
"@/*": [
|
||||||
}
|
"./*",
|
||||||
|
"../../node_modules/*"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
"plugins": [
|
||||||
"exclude": ["node_modules"]
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue