diff --git a/apps/leafcutter/app/_components/AppProvider.tsx b/apps/leafcutter/app/_components/AppProvider.tsx index cc4c879..19b8120 100644 --- a/apps/leafcutter/app/_components/AppProvider.tsx +++ b/apps/leafcutter/app/_components/AppProvider.tsx @@ -8,7 +8,7 @@ import { useState, PropsWithChildren, } from "react"; -import { colors, typography } from "styles/theme"; +import { colors, typography } from "@/app/_styles/theme"; const basePath = process.env.GITLAB_CI ? "/link/link-stack/apps/leafcutter" diff --git a/apps/leafcutter/app/_components/GettingStartedDialog.tsx b/apps/leafcutter/app/_components/GettingStartedDialog.tsx index c50e4ca..20f688e 100644 --- a/apps/leafcutter/app/_components/GettingStartedDialog.tsx +++ b/apps/leafcutter/app/_components/GettingStartedDialog.tsx @@ -3,7 +3,7 @@ import { FC, useState } from "react"; import { Dialog, Box, Grid, Checkbox, IconButton } from "@mui/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 { useAppContext } from "./AppProvider"; @@ -62,9 +62,11 @@ export const GettingStartedDialog: FC = () => { typography: { h4 }, } = useAppContext(); const t = useTranslate(); - const [completedItems, setCompletedItems] = useState([] as any[]); 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) => { if (completedItems.includes(item)) { setCompletedItems(completedItems.filter((i) => i !== item)); @@ -92,7 +94,7 @@ export const GettingStartedDialog: FC = () => { {t("getStartedChecklist")} - router.push(router.pathname)}> + router.push(pathname)}> diff --git a/apps/leafcutter/app/_components/HelpButton.tsx b/apps/leafcutter/app/_components/HelpButton.tsx index f4e2bb0..6d279dc 100644 --- a/apps/leafcutter/app/_components/HelpButton.tsx +++ b/apps/leafcutter/app/_components/HelpButton.tsx @@ -1,20 +1,21 @@ "use client"; import { FC, useState } from "react"; -import { useRouter } from "next/router"; +import { useRouter, usePathname } from "next/navigation"; import { Button } from "@mui/material"; import { QuestionMark as QuestionMarkIcon } from "@mui/icons-material"; import { useAppContext } from "./AppProvider"; export const HelpButton: FC = () => { const router = useRouter(); + const pathname = usePathname(); const [helpActive, setHelpActive] = useState(false); const { colors: { leafcutterElectricBlue }, } = useAppContext(); const onClick = () => { if (helpActive) { - router.push(router.pathname); + router.push(pathname); } else { router.push("/?tooltip=welcome"); } diff --git a/apps/leafcutter/app/_components/Home.tsx b/apps/leafcutter/app/_components/Home.tsx index 58c9776..806e656 100644 --- a/apps/leafcutter/app/_components/Home.tsx +++ b/apps/leafcutter/app/_components/Home.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, FC } from "react"; -import { useRouter } from "next/router"; +import { useRouter, usePathname } from "next/navigation"; import Link from "next/link"; import ReactMarkdown from "react-markdown"; import { Grid, Button } from "@mui/material"; @@ -14,11 +14,11 @@ import { useAppContext } from "@/app/_components/AppProvider"; type HomeProps = { visualizations: any; - embedded: boolean; }; -export const Home: FC = ({ visualizations, embedded }) => { +export const Home: FC = ({ visualizations }) => { const router = useRouter(); + const pathname = usePathname(); const cookieName = "homeIntroComplete"; const [cookies, setCookie] = useCookies([cookieName]); const t = useTranslate(); @@ -31,7 +31,7 @@ export const Home: FC = ({ visualizations, embedded }) => { useEffect(() => { if (homeIntroComplete === 0) { setCookie(cookieName, `${1}`, { path: "/" }); - router.push(`${router.pathname}?tooltip=welcome`); + router.push(`${pathname}?tooltip=welcome`); } }, [homeIntroComplete, router, setCookie]); diff --git a/apps/leafcutter/app/_components/LanguageSelect.tsx b/apps/leafcutter/app/_components/LanguageSelect.tsx index c965b0c..9887691 100644 --- a/apps/leafcutter/app/_components/LanguageSelect.tsx +++ b/apps/leafcutter/app/_components/LanguageSelect.tsx @@ -1,6 +1,6 @@ "use client"; -import { useRouter } from "next/router"; +import { useRouter } from "next/navigation"; import { IconButton, Menu, MenuItem, Box } from "@mui/material"; import { KeyboardArrowDown as KeyboardArrowDownIcon } from "@mui/icons-material"; import { @@ -17,6 +17,7 @@ export const LanguageSelect = () => { } = useAppContext(); const router = useRouter(); const locales: any = { en: "English", fr: "Français" }; + const locale = "en"; const popupState = usePopupState({ variant: "popover", popupId: "language" }); return ( @@ -38,7 +39,7 @@ export const LanguageSelect = () => { }, }} > - {locales[router.locale as any] ?? locales.en} + {locales[locale as any] ?? locales.en} @@ -46,7 +47,7 @@ export const LanguageSelect = () => { { - router.push(router.route, router.route, { locale }); + // router.push(router.route, router.route, { locale }); popupState.close(); }} > diff --git a/apps/leafcutter/app/_components/MultiProvider.tsx b/apps/leafcutter/app/_components/MultiProvider.tsx index 4785653..ecbf7fe 100644 --- a/apps/leafcutter/app/_components/MultiProvider.tsx +++ b/apps/leafcutter/app/_components/MultiProvider.tsx @@ -1,3 +1,5 @@ +"use client"; + /* eslint-disable react/jsx-props-no-spreading */ import { FC, PropsWithChildren } from "react"; import { SessionProvider } from "next-auth/react"; @@ -5,19 +7,17 @@ import { CssBaseline } from "@mui/material"; import { CookiesProvider } from "react-cookie"; import { I18n } from "react-polyglot"; import { AdapterDateFns } from "@mui/x-date-pickers-pro/AdapterDateFns"; -("use client"); - import { LocalizationProvider } from "@mui/x-date-pickers-pro"; import { AppProvider } from "@/app/_components/AppProvider"; import { NextAppDirEmotionCacheProvider } from "tss-react/next/appDir"; -import en from "locales/en.json"; -import fr from "locales/fr.json"; +import en from "app/_locales/en.json"; +import fr from "app/_locales/fr.json"; import "@fontsource/poppins/400.css"; import "@fontsource/poppins/700.css"; import "@fontsource/roboto/400.css"; import "@fontsource/roboto/700.css"; import "@fontsource/playfair-display/900.css"; -import "styles/global.css"; +import "app/_styles/global.css"; import { LicenseInfo } from "@mui/x-data-grid-pro"; LicenseInfo.setLicenseKey(process.env.MUI_LICENSE_KEY ?? ""); diff --git a/apps/leafcutter/app/_components/QueryText.tsx b/apps/leafcutter/app/_components/QueryText.tsx index 479fcdf..776ceeb 100644 --- a/apps/leafcutter/app/_components/QueryText.tsx +++ b/apps/leafcutter/app/_components/QueryText.tsx @@ -4,7 +4,7 @@ import { FC, useState, useEffect } from "react"; import { Box, Grid } from "@mui/material"; import { useTranslate } from "react-polyglot"; import taxonomy from "app/_config/taxonomy.json"; -import { colors } from "styles/theme"; +import { colors } from "@/app/_styles/theme"; import { useAppContext } from "./AppProvider"; export const QueryText: FC = () => { diff --git a/apps/leafcutter/app/_components/Sidebar.tsx b/apps/leafcutter/app/_components/Sidebar.tsx index cab554f..9d0e58e 100644 --- a/apps/leafcutter/app/_components/Sidebar.tsx +++ b/apps/leafcutter/app/_components/Sidebar.tsx @@ -18,7 +18,7 @@ import { Drawer, } from "@mui/material"; import Link from "next/link"; -import { useRouter } from "next/router"; +import { usePathname } from "next/navigation"; import { useTranslate } from "react-polyglot"; import { useAppContext } from "@/app/_components/AppProvider"; import { Tooltip } from "@/app/_components/Tooltip"; @@ -101,8 +101,8 @@ interface SidebarProps { export const Sidebar: FC = ({ open }) => { const t = useTranslate(); - const router = useRouter(); - const section = router.pathname.split("/")[1]; + const pathname = usePathname(); + const section = pathname.split("/")[1]; const { colors: { white }, // leafcutterElectricBlue, leafcutterLightBlue, } = useAppContext(); diff --git a/apps/leafcutter/app/_components/Tooltip.tsx b/apps/leafcutter/app/_components/Tooltip.tsx index 3758dde..aa2f25d 100644 --- a/apps/leafcutter/app/_components/Tooltip.tsx +++ b/apps/leafcutter/app/_components/Tooltip.tsx @@ -2,7 +2,7 @@ /* eslint-disable react/require-default-props */ import { FC } from "react"; -import { useRouter } from "next/router"; +import { useRouter, usePathname, useSearchParams } from "next/navigation"; import { Box, Grid, @@ -40,7 +40,9 @@ export const Tooltip: FC = ({ colors: { white, leafcutterElectricBlue, almostBlack }, } = useAppContext(); 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 showNavigation = true; @@ -51,7 +53,7 @@ export const Tooltip: FC = ({ - router.push(router.pathname)}> + router.push(pathname)}> = ({ color: leafcutterElectricBlue, textTransform: "none", }} - onClick={() => router.push(router.pathname)} + onClick={() => router.push(pathname)} > {t("done")} diff --git a/apps/leafcutter/app/_components/Welcome.tsx b/apps/leafcutter/app/_components/Welcome.tsx index e1400ca..25f7865 100644 --- a/apps/leafcutter/app/_components/Welcome.tsx +++ b/apps/leafcutter/app/_components/Welcome.tsx @@ -8,9 +8,12 @@ import { useAppContext } from "./AppProvider"; export const Welcome = () => { const t = useTranslate(); const { data: session } = useSession(); + /* const { user: { name }, } = session as any; + */ + const name = "Test User"; const { colors: { white, leafcutterElectricBlue }, typography: { h1, h4, p }, diff --git a/apps/leafcutter/app/_components/WelcomeDialog.tsx b/apps/leafcutter/app/_components/WelcomeDialog.tsx index 009d8c5..59cc4d4 100644 --- a/apps/leafcutter/app/_components/WelcomeDialog.tsx +++ b/apps/leafcutter/app/_components/WelcomeDialog.tsx @@ -1,7 +1,7 @@ "use client"; 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 { useTranslate } from "react-polyglot"; import { useAppContext } from "./AppProvider"; @@ -9,13 +9,14 @@ import { useAppContext } from "./AppProvider"; export const WelcomeDialog = () => { // const t = useTranslate(); const router = useRouter(); + const searchParams = useSearchParams(); // const { data: session } = useSession(); // const { user } = session; const { colors: { white, leafcutterElectricBlue }, typography: { h1, h6, p }, } = useAppContext(); - const activeTooltip = router.query.tooltip?.toString(); + const activeTooltip = searchParams.get('tooltip')?.toString(); const open = activeTooltip === "welcome"; return ( diff --git a/apps/leafcutter/app/_lib/auth.ts b/apps/leafcutter/app/_lib/auth.ts new file mode 100644 index 0000000..a8c9426 --- /dev/null +++ b/apps/leafcutter/app/_lib/auth.ts @@ -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, +}; diff --git a/apps/leafcutter/app/_lib/createEmotionCache.ts b/apps/leafcutter/app/_lib/createEmotionCache.ts deleted file mode 100644 index 79c4a1a..0000000 --- a/apps/leafcutter/app/_lib/createEmotionCache.ts +++ /dev/null @@ -1,5 +0,0 @@ -import createCache from "@emotion/cache"; - -export default function createEmotionCache() { - return createCache({ key: "css" }); -} diff --git a/apps/leafcutter/app/_lib/opensearch.ts b/apps/leafcutter/app/_lib/opensearch.ts index 10985d0..46730f6 100644 --- a/apps/leafcutter/app/_lib/opensearch.ts +++ b/apps/leafcutter/app/_lib/opensearch.ts @@ -8,10 +8,16 @@ const globalIndex = ".kibana_1"; const dataIndexName = "sample_tagged_tickets"; 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({ node: baseURL, + auth: { + username: process.env.OPENSEARCH_USERNAME!, + password: process.env.OPENSEARCH_PASSWORD!, + }, ssl: { rejectUnauthorized: false, }, @@ -532,6 +538,7 @@ export const getTrends = async (limit: number) => { export const getTemplates = async (limit: number) => { const client = createClient(); + const query = { query: { bool: { @@ -546,11 +553,14 @@ export const getTemplates = async (limit: number) => { }, }, }; + + const rawResponse = await client.search({ index: globalIndex, size: limit, body: query, }); + const response = rawResponse.body; const { hits: { hits }, diff --git a/apps/leafcutter/app/_lib/utils.ts b/apps/leafcutter/app/_lib/utils.ts deleted file mode 100644 index 69aad81..0000000 --- a/apps/leafcutter/app/_lib/utils.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { GetServerSidePropsContext } from "next"; - -export const getEmbedded = (context: GetServerSidePropsContext) => - context.req.headers["x-leafcutter-embedded"] === "true"; diff --git a/apps/leafcutter/locales/en.json b/apps/leafcutter/app/_locales/en.json similarity index 100% rename from apps/leafcutter/locales/en.json rename to apps/leafcutter/app/_locales/en.json diff --git a/apps/leafcutter/locales/fr.json b/apps/leafcutter/app/_locales/fr.json similarity index 100% rename from apps/leafcutter/locales/fr.json rename to apps/leafcutter/app/_locales/fr.json diff --git a/apps/leafcutter/styles/global.css b/apps/leafcutter/app/_styles/global.css similarity index 100% rename from apps/leafcutter/styles/global.css rename to apps/leafcutter/app/_styles/global.css diff --git a/apps/leafcutter/styles/theme.ts b/apps/leafcutter/app/_styles/theme.ts similarity index 100% rename from apps/leafcutter/styles/theme.ts rename to apps/leafcutter/app/_styles/theme.ts diff --git a/apps/leafcutter/app/about/_components/About.tsx b/apps/leafcutter/app/about/_components/About.tsx new file mode 100644 index 0000000..f132f53 --- /dev/null +++ b/apps/leafcutter/app/about/_components/About.tsx @@ -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 ( + <> + + + + + {t("aboutLeafcutterTitle")} + + + {t("aboutLeafcutterDescription")} + + + + + + + + + + + + {t("whereDataComesFromTitle")} + + {t("whereDataComesFromDescription") + .split("\n") + .map((line: string, i: number) => ( + + {line} + + ))} + + + + {t("projectSupportTitle")} + + {t("projectSupportDescription") + .split("\n") + .map((line: string, i: number) => ( + + {line} + + ))} + + + + + + + + {t("interestedInLeafcutterTitle")} + + {t("interestedInLeafcutterDescription") + .split("\n") + .map((line: string, i: number) => ( + + {line} + + ))} + + + + + + + ); +}; + diff --git a/apps/leafcutter/app/about/page.tsx b/apps/leafcutter/app/about/page.tsx index 29e80e2..2dc1b39 100644 --- a/apps/leafcutter/app/about/page.tsx +++ b/apps/leafcutter/app/about/page.tsx @@ -1,173 +1,5 @@ -import { NextPage, GetServerSideProps, GetServerSidePropsContext } from "next"; -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"; +import { About } from './_components/About'; -type AboutProps = { - embedded: boolean; -}; - -const About: NextPage = ({ embedded }) => { - const t = useTranslate(); - const { - colors: { white, leafcutterElectricBlue, cdrLinkOrange }, - typography: { h1, h4, p }, - } = useAppContext(); - - return ( - <> - - - - - {t("aboutLeafcutterTitle")} - - - {t("aboutLeafcutterDescription")} - - - - - - - - - - - - {t("whereDataComesFromTitle")} - - {t("whereDataComesFromDescription") - .split("\n") - .map((line: string, i: number) => ( - - {line} - - ))} - - - - {t("projectSupportTitle")} - - {t("projectSupportDescription") - .split("\n") - .map((line: string, i: number) => ( - - {line} - - ))} - - - - - - - - {t("interestedInLeafcutterTitle")} - - {t("interestedInLeafcutterDescription") - .split("\n") - .map((line: string, i: number) => ( - - {line} - - ))} - - - - - - - ); -}; - -export default About; - -export const getServerSideProps: GetServerSideProps = async ( - context: GetServerSidePropsContext -) => ({ props: { embedded: getEmbedded(context) } }); +export default function Page() { + return ; +} diff --git a/apps/leafcutter/app/api/auth/[...nextauth]/route.ts b/apps/leafcutter/app/api/auth/[...nextauth]/route.ts index 145e34c..4a9a00b 100644 --- a/apps/leafcutter/app/api/auth/[...nextauth]/route.ts +++ b/apps/leafcutter/app/api/auth/[...nextauth]/route.ts @@ -1,19 +1,6 @@ import NextAuth from "next-auth"; -import Google from "next-auth/providers/google"; -import Apple from "next-auth/providers/apple"; +import { authOptions } from "@/app/_lib/auth"; -const handler = NextAuth({ - 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, -}); +const handler = NextAuth(authOptions); export { handler as GET, handler as POST }; diff --git a/apps/leafcutter/app/api/searches/create.ts b/apps/leafcutter/app/api/searches/create.ts deleted file mode 100644 index 31c9e2c..0000000 --- a/apps/leafcutter/app/api/searches/create.ts +++ /dev/null @@ -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); -}; - - - - diff --git a/apps/leafcutter/app/api/searches/create/route.ts b/apps/leafcutter/app/api/searches/create/route.ts new file mode 100644 index 0000000..f4448f3 --- /dev/null +++ b/apps/leafcutter/app/api/searches/create/route.ts @@ -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); +}; + + + + diff --git a/apps/leafcutter/app/api/searches/delete.ts b/apps/leafcutter/app/api/searches/delete.ts deleted file mode 100644 index 83cfc1e..0000000 --- a/apps/leafcutter/app/api/searches/delete.ts +++ /dev/null @@ -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; - - - diff --git a/apps/leafcutter/app/api/searches/delete/route.ts b/apps/leafcutter/app/api/searches/delete/route.ts new file mode 100644 index 0000000..b960336 --- /dev/null +++ b/apps/leafcutter/app/api/searches/delete/route.ts @@ -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 }); +}; + + + + diff --git a/apps/leafcutter/app/api/searches/list.ts b/apps/leafcutter/app/api/searches/list.ts deleted file mode 100644 index eabb048..0000000 --- a/apps/leafcutter/app/api/searches/list.ts +++ /dev/null @@ -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; diff --git a/apps/leafcutter/app/api/searches/list/route.ts b/apps/leafcutter/app/api/searches/list/route.ts new file mode 100644 index 0000000..84e0c2d --- /dev/null +++ b/apps/leafcutter/app/api/searches/list/route.ts @@ -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); +}; diff --git a/apps/leafcutter/app/api/trends/recent.ts b/apps/leafcutter/app/api/trends/recent.ts deleted file mode 100644 index 94b3cc1..0000000 --- a/apps/leafcutter/app/api/trends/recent.ts +++ /dev/null @@ -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); -}; - diff --git a/apps/leafcutter/app/api/trends/recent/route.ts b/apps/leafcutter/app/api/trends/recent/route.ts new file mode 100644 index 0000000..65f328c --- /dev/null +++ b/apps/leafcutter/app/api/trends/recent/route.ts @@ -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); +}; + diff --git a/apps/leafcutter/app/api/upload/index.ts b/apps/leafcutter/app/api/upload/index.ts index 80bb338..4f3223e 100644 --- a/apps/leafcutter/app/api/upload/index.ts +++ b/apps/leafcutter/app/api/upload/index.ts @@ -1,12 +1,13 @@ /* eslint-disable no-restricted-syntax */ -import { NextApiRequest, NextApiResponse } from "next"; +import { NextRequest, NextResponse } from "next/server"; import { Client } from "@opensearch-project/opensearch"; import { v4 as uuid } from "uuid"; import taxonomy from "app/_config/taxonomy.json"; import unRegions from "app/_config/unRegions.json"; -const handler = async (req: NextApiRequest, res: NextApiResponse) => { - const { headers: { authorization }, body: { tickets } } = req; +export const POST = async (req: NextRequest) => { + const { tickets } = await req.json(); + const authorization = req.headers.get("authorization"); const baseURL = `https://${process.env.OPENSEARCH_URL}`; const client = new Client({ node: baseURL, @@ -48,7 +49,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { } const results = { succeeded, failed }; - return res.json(results); + + return NextResponse.json(results); }; -export default handler; diff --git a/apps/leafcutter/app/api/visualizations/create.ts b/apps/leafcutter/app/api/visualizations/create.ts deleted file mode 100644 index 06eb2cf..0000000 --- a/apps/leafcutter/app/api/visualizations/create.ts +++ /dev/null @@ -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; - diff --git a/apps/leafcutter/app/api/visualizations/create/route.ts b/apps/leafcutter/app/api/visualizations/create/route.ts new file mode 100644 index 0000000..c0b1a95 --- /dev/null +++ b/apps/leafcutter/app/api/visualizations/create/route.ts @@ -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 }); +}; + diff --git a/apps/leafcutter/app/api/visualizations/delete.ts b/apps/leafcutter/app/api/visualizations/delete.ts deleted file mode 100644 index 73aba5a..0000000 --- a/apps/leafcutter/app/api/visualizations/delete.ts +++ /dev/null @@ -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; - diff --git a/apps/leafcutter/app/api/visualizations/delete/route.ts b/apps/leafcutter/app/api/visualizations/delete/route.ts new file mode 100644 index 0000000..56c85a9 --- /dev/null +++ b/apps/leafcutter/app/api/visualizations/delete/route.ts @@ -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 }); +}; + + diff --git a/apps/leafcutter/app/api/visualizations/query.ts b/apps/leafcutter/app/api/visualizations/query.ts deleted file mode 100644 index 89ccca2..0000000 --- a/apps/leafcutter/app/api/visualizations/query.ts +++ /dev/null @@ -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; - diff --git a/apps/leafcutter/app/api/visualizations/query/route.ts b/apps/leafcutter/app/api/visualizations/query/route.ts new file mode 100644 index 0000000..9ad408c --- /dev/null +++ b/apps/leafcutter/app/api/visualizations/query/route.ts @@ -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); +}; + + diff --git a/apps/leafcutter/app/api/visualizations/update.ts b/apps/leafcutter/app/api/visualizations/update.ts deleted file mode 100644 index 7b8e2e8..0000000 --- a/apps/leafcutter/app/api/visualizations/update.ts +++ /dev/null @@ -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; - diff --git a/apps/leafcutter/app/api/visualizations/update/route.ts b/apps/leafcutter/app/api/visualizations/update/route.ts new file mode 100644 index 0000000..ec4807a --- /dev/null +++ b/apps/leafcutter/app/api/visualizations/update/route.ts @@ -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; + diff --git a/apps/leafcutter/app/create/_components/Create.tsx b/apps/leafcutter/app/create/_components/Create.tsx new file mode 100644 index 0000000..aabb0a3 --- /dev/null +++ b/apps/leafcutter/app/create/_components/Create.tsx @@ -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 = ({ 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 ( + <> + + + {/* + + */} + + + + {t("searchAndCreateTitle")} + + + + + {t("searchAndCreateSubtitle")} + + + {/* + + {t("searchAndCreateDescription")} + + */} + + + + + + ); +}; + diff --git a/apps/leafcutter/app/create/page.tsx b/apps/leafcutter/app/create/page.tsx index dfe6baa..ab0329d 100644 --- a/apps/leafcutter/app/create/page.tsx +++ b/apps/leafcutter/app/create/page.tsx @@ -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 { useAppContext } from "@/app/_components/AppProvider"; -import { PageHeader } from "@/app/_components/PageHeader"; -import { VisualizationBuilder } from "@/app/_components/VisualizationBuilder"; -import { getEmbedded } from "@/app/_lib/utils"; +import { Create } from "./_components/Create"; -type CreateProps = { - templates: any; - embedded: boolean; -}; - -const Create: FC = ({ 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 ( - <> - - - {/* - - */} - - - - {t("searchAndCreateTitle")} - - - - - {t("searchAndCreateSubtitle")} - - - {/* - - {t("searchAndCreateDescription")} - - */} - - - - - - - ); -}; - -export default Create; - -export const getServerSideProps: GetServerSideProps = async ( - context: GetServerSidePropsContext -) => { +export default async function Page() { const templates = await getTemplates(100); + console.log({templates}); - return { props: { templates, embedded: getEmbedded(context) } }; + return ; }; diff --git a/apps/leafcutter/app/faq/_components/FAQ.tsx b/apps/leafcutter/app/faq/_components/FAQ.tsx new file mode 100644 index 0000000..0ab21d3 --- /dev/null +++ b/apps/leafcutter/app/faq/_components/FAQ.tsx @@ -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 ( + <> + + + + + {t("frequentlyAskedQuestionsTitle")} + + + {t("frequentlyAskedQuestionsSubtitle")} + + + {t("frequentlyAskedQuestionsDescription")} + + + + + {questions.map((q: any, index: number) => ( + + ))} + + ); +}; diff --git a/apps/leafcutter/app/faq/page.tsx b/apps/leafcutter/app/faq/page.tsx index e3bf51d..93c7447 100644 --- a/apps/leafcutter/app/faq/page.tsx +++ b/apps/leafcutter/app/faq/page.tsx @@ -1,111 +1,5 @@ -import { useTranslate } from "react-polyglot"; -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"; +import { FAQ } from "./_components/FAQ"; -type FAQProps = { - embedded: boolean; -}; - -const FAQ: NextPage = ({ 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 ( - <> - - - - - {t("frequentlyAskedQuestionsTitle")} - - - {t("frequentlyAskedQuestionsSubtitle")} - - - {t("frequentlyAskedQuestionsDescription")} - - - - - {questions.map((q: any, index: number) => ( - - ))} - - ); -}; - -export default FAQ; - -export const getServerSideProps: GetServerSideProps = async ( - context: GetServerSidePropsContext -) => ({ props: { embedded: getEmbedded(context) } }); +export default function Page() { + return ; +} diff --git a/apps/leafcutter/app/layout.tsx b/apps/leafcutter/app/layout.tsx index a6c3119..f317ecd 100644 --- a/apps/leafcutter/app/layout.tsx +++ b/apps/leafcutter/app/layout.tsx @@ -1,16 +1,16 @@ import { ReactNode } from "react"; import { Metadata } from "next"; -import "./_styles/global.css"; +import "app/_styles/global.css"; import "@fontsource/poppins/400.css"; import "@fontsource/poppins/700.css"; import "@fontsource/roboto/400.css"; import "@fontsource/roboto/700.css"; import "@fontsource/playfair-display/900.css"; -import "styles/global.css"; // import getConfig from "next/config"; // import { LicenseInfo } from "@mui/x-data-grid-pro"; import { MultiProvider } from "app/_components/MultiProvider"; import { InternalLayout } from "app/_components/InternalLayout"; +import { headers } from 'next/headers' export const metadata: Metadata = { title: "Leafcutter", @@ -21,6 +21,8 @@ type LayoutProps = { }; export default function Layout({ children }: LayoutProps) { + const allHeaders = headers(); + const embedded = Boolean(allHeaders.get('x-leafcutter-embedded')); // const { publicRuntimeConfig } = getConfig(); // LicenseInfo.setLicenseKey(publicRuntimeConfig.muiLicenseKey); @@ -28,7 +30,7 @@ export default function Layout({ children }: LayoutProps) { - {children} + {children} diff --git a/apps/leafcutter/app/login/_components/Login.tsx b/apps/leafcutter/app/login/_components/Login.tsx new file mode 100644 index 0000000..4aec669 --- /dev/null +++ b/apps/leafcutter/app/login/_components/Login.tsx @@ -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 = ({ 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 ( + <> + + + + + + + + + + + + + + + {t("welcomeToLeafcutter")} + + + {t("welcomeToLeafcutterDescription")} + + + + {!session ? ( + + + + signIn("google", { + callbackUrl: `${window.location.origin}/setup`, + }) + } + > + + {`${t("signInWith")} Google`} + + + + + signIn("apple", { + callbackUrl: `${window.location.origin}/setup`, + }) + } + > + + {`${t("signInWith")} Apple`} + + + + + {t("dontHaveAccount")}{" "} + + {t("requestAccessHere")} + + + + + ) : null} + {session ? ( + <> + + {`${t("welcome")}, ${ + session.user.name ?? session.user.email + }.`} + + {t("goHome")} + + ) : null} + + + + + ); +}; diff --git a/apps/leafcutter/app/login/page.tsx b/apps/leafcutter/app/login/page.tsx index 18d8ee8..5c8263b 100644 --- a/apps/leafcutter/app/login/page.tsx +++ b/apps/leafcutter/app/login/page.tsx @@ -1,126 +1,16 @@ -import Head from "next/head"; -import { NextPage } from "next"; -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, getSession } from "next-auth/react"; -import { useAppContext } from "@/app/_components/AppProvider"; +import { Metadata } from "next"; +import { getServerSession } from "next-auth"; +import { authOptions } from "app/_lib/auth"; +import { Login } from "./_components/Login"; -type LoginProps = { - session: any; +export const metadata: Metadata = { + title: "Login", }; -const Login: NextPage = ({ session }) => { - const t = useTranslate(); - const { - colors: { leafcutterElectricBlue, lightGray }, - typography: { h1, h4 }, - } = useAppContext(); - const buttonStyles = { - backgroundColor: lightGray, - borderRadius: 500, - width: "100%", - fontSize: "16px", - fontWeight: "bold", - }; +export default async function Page() { + const session = await getServerSession(authOptions); - return ( - <> - - Leafcutter: Login - - - - - - - - - - - - - - - - {t("welcomeToLeafcutter")} - - - {t("welcomeToLeafcutterDescription")} - - - - {!session ? ( - - - - signIn("google", { - callbackUrl: `${window.location.origin}/setup`, - }) - } - > - - {`${t("signInWith")} Google`} - - - - - signIn("apple", { - callbackUrl: `${window.location.origin}/setup`, - }) - } - > - - {`${t("signInWith")} Apple`} - - - - - {t("dontHaveAccount")}{" "} - - {t("requestAccessHere")} - - - - - ) : null} - {session ? ( - <> - - {`${t("welcome")}, ${ - session.user.name ?? session.user.email - }.`} - - {t("goHome")} - - ) : null} - - - - - ); -}; - -export default Login; - -export async function getServerSideProps(context: any) { - const session = (await getSession(context)) ?? null; - - return { - props: { session }, - }; + return ; } + + diff --git a/apps/leafcutter/app/page.tsx b/apps/leafcutter/app/page.tsx index 34c8b7f..140f4b8 100644 --- a/apps/leafcutter/app/page.tsx +++ b/apps/leafcutter/app/page.tsx @@ -1,6 +1,5 @@ import { getSession } from "next-auth/react"; import { getUserVisualizations } from "@/app/_lib/opensearch"; -import { getEmbedded } from "@/app/_lib/utils"; import { Home } from "@/app/_components/Home"; export default async function Page() { @@ -10,7 +9,6 @@ export default async function Page() { session?.user?.email ?? "none", 20 ); - const embedded = false; // getEmbedded(context); - return ; + return ; } diff --git a/apps/leafcutter/app/setup/page.tsx b/apps/leafcutter/app/setup/page.tsx index decf509..0f13284 100644 --- a/apps/leafcutter/app/setup/page.tsx +++ b/apps/leafcutter/app/setup/page.tsx @@ -1,6 +1,6 @@ import { useLayoutEffect } from "react"; import { NextPage } from "next"; -import { useRouter } from "next/router"; +import { useRouter } from "next/navigation"; import { Grid, CircularProgress } from "@mui/material"; import Iframe from "react-iframe"; import { useAppContext } from "@/app/_components/AppProvider"; diff --git a/apps/leafcutter/app/trends/_components/Trends.tsx b/apps/leafcutter/app/trends/_components/Trends.tsx new file mode 100644 index 0000000..c10ca7d --- /dev/null +++ b/apps/leafcutter/app/trends/_components/Trends.tsx @@ -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 = ({ visualizations }) => { + const t = useTranslate(); + const { + colors: { cdrLinkOrange }, + typography: { h1, h4, p }, + } = useAppContext(); + + return ( + <> + + + {/* + + */} + + + + {t("trendsTitle")} + + + + + {t("trendsSubtitle")} + + + + + {t("trendsDescription")} + + + + + + + {visualizations.map((visualization: any, index: number) => ( + + ))} + + + ); +}; diff --git a/apps/leafcutter/app/trends/page.tsx b/apps/leafcutter/app/trends/page.tsx index ccc817b..51e0c4e 100644 --- a/apps/leafcutter/app/trends/page.tsx +++ b/apps/leafcutter/app/trends/page.tsx @@ -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 { PageHeader } from "@/app/_components/PageHeader"; -import { VisualizationCard } from "@/app/_components/VisualizationCard"; -import { useAppContext } from "@/app/_components/AppProvider"; -import { getEmbedded } from "@/app/_lib/utils"; +import { Trends } from "./_components/Trends"; -type TrendsProps = { - visualizations: any; - embedded: boolean; -}; - -const Trends: NextPage = ({ visualizations, embedded }) => { - const t = useTranslate(); - const { - colors: { cdrLinkOrange }, - typography: { h1, h4, p }, - } = useAppContext(); - - return ( - <> - - - {/* - - */} - - - - {t("trendsTitle")} - - - - - {t("trendsSubtitle")} - - - - - {t("trendsDescription")} - - - - - - - {visualizations.map((visualization: any, index: number) => ( - - ))} - - - ); -}; - -export default Trends; - -export const getServerSideProps: GetServerSideProps = async ( - context: GetServerSidePropsContext -) => { +export default async function Page() { const visualizations = await getTrends(25); - return { props: { visualizations, embedded: getEmbedded(context) } }; -}; + return ; +} diff --git a/apps/leafcutter/app/visualizations/[...visualizationID]/page.tsx b/apps/leafcutter/app/visualizations/[...visualizationID]/page.tsx index 3491f5d..52fffee 100644 --- a/apps/leafcutter/app/visualizations/[...visualizationID]/page.tsx +++ b/apps/leafcutter/app/visualizations/[...visualizationID]/page.tsx @@ -1,26 +1,8 @@ /* eslint-disable no-underscore-dangle */ -import { NextPage, GetServerSideProps, GetServerSidePropsContext } from "next"; import { Client } from "@opensearch-project/opensearch"; import { VisualizationDetail } from "@/app/_components/VisualizationDetail"; -import { getEmbedded } from "@/app/_lib/utils"; - -type VisualizationProps = { - visualization: any; - embedded: boolean; -}; - -const Visualization: NextPage = ({ - visualization, - embedded, -}) => ; - -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 client = new Client({ node, @@ -36,7 +18,6 @@ export const getServerSideProps: GetServerSideProps = async ( const response = rawResponse.body; const hits = response.hits.hits.filter( - // @ts-expect-error (hit: any) => hit._id.split(":")[1] === visualizationID[0] ); const hit = hits[0]; @@ -49,5 +30,19 @@ export const getServerSideProps: GetServerSideProps = async ( }?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 ; +} + + diff --git a/apps/leafcutter/middleware.ts b/apps/leafcutter/middleware.ts index 383e669..1840b93 100644 --- a/apps/leafcutter/middleware.ts +++ b/apps/leafcutter/middleware.ts @@ -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"; +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( - () => { }, + checkRewrites, { pages: { signIn: `/login`, @@ -14,32 +63,21 @@ export default withAuth( headers, } = req; - return true; - /* - const { - publicRuntimeConfig: { embedded }, - } = getConfig(); - - if (embedded) { - return true; - } - - // check login page - const parsedURL = new URL(url); - if (parsedURL.pathname.startsWith('/login')) { - return true; - } - - // check session auth - const authorizedDomains = ["redaranj.com", "digiresilience.org"]; - const userDomain = token?.email?.toLowerCase().split("@").pop() ?? "unauthorized.net"; - - if (authorizedDomains.includes(userDomain)) { - return true; - } - - return false; - */ + // check login page + const parsedURL = new URL(url); + if (parsedURL.pathname.startsWith('/login')) { + return true; + } + + // check session auth + const authorizedDomains = ["redaranj.com", "digiresilience.org"]; + const userDomain = token?.email?.toLowerCase().split("@").pop() ?? "unauthorized.net"; + + if (authorizedDomains.includes(userDomain)) { + return true; + } + + return false; }, } } diff --git a/apps/leafcutter/next.config.js b/apps/leafcutter/next.config.js index 1d649c7..128e197 100644 --- a/apps/leafcutter/next.config.js +++ b/apps/leafcutter/next.config.js @@ -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 = { publicRuntimeConfig: { embedded: true - }, + },/* basePath: "/proxy/leafcutter", assetPrefix: "/proxy/leafcutter", i18n: { locales: ["en", "fr"], defaultLocale: "en", }, - +*/ /* rewrites: async () => ({ 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' + } + ], + }, + ] + } }; diff --git a/apps/leafcutter/package.json b/apps/leafcutter/package.json index b6cd803..0fce197 100644 --- a/apps/leafcutter/package.json +++ b/apps/leafcutter/package.json @@ -3,7 +3,7 @@ "version": "0.2.0", "scripts": { "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", "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", diff --git a/apps/leafcutter/tsconfig.json b/apps/leafcutter/tsconfig.json index 56ddbc7..e82829e 100644 --- a/apps/leafcutter/tsconfig.json +++ b/apps/leafcutter/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -16,9 +20,24 @@ "incremental": true, "baseUrl": ".", "paths": { - "@/*": ["./*", "../../node_modules/*"] - } + "@/*": [ + "./*", + "../../node_modules/*" + ] + }, + "plugins": [ + { + "name": "next" + } + ] }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], - "exclude": ["node_modules"] + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] }