diff --git a/README.md b/README.md
index 2ef2a992b..8e87fb551 100644
--- a/README.md
+++ b/README.md
@@ -219,3 +219,5 @@ To configure Keycloak:
- KEYCLOAK_ISSUER: http://localhost:8080/realms/master
[keycloak-admin]: http://localhost:8080/admin/master/console/#/
+
+Remember to add NEXTAUTH_SECRET variable to your .env file!
diff --git a/app/components/header.tsx b/app/components/header.tsx
index af6a071a5..c9c61f2b6 100644
--- a/app/components/header.tsx
+++ b/app/components/header.tsx
@@ -15,8 +15,7 @@ import { DataSourceMenu } from "@/components/data-source-menu";
import Flex from "@/components/flex";
import { LanguageMenu } from "@/components/language-menu";
import { SOURCE_OPTIONS } from "@/domain/datasource/constants";
-
-import LoginMenu from "./login-menu";
+import { LoginMenu } from "@/login/components/login-menu";
const DEFAULT_HEADER_PROGRESS = 100;
diff --git a/app/configurator/components/layout.tsx b/app/configurator/components/layout.tsx
index 74faa2c39..cf29ed9dc 100644
--- a/app/configurator/components/layout.tsx
+++ b/app/configurator/components/layout.tsx
@@ -3,6 +3,7 @@ import { makeStyles } from "@mui/styles";
import clsx from "clsx";
import React from "react";
+import { HEADER_HEIGHT } from "@/components/header";
import { DRAWER_WIDTH } from "@/configurator/components/drawer";
const useStyles = makeStyles((theme: Theme) => ({
@@ -28,10 +29,9 @@ const useStyles = makeStyles((theme: Theme) => ({
},
panelLayout: {
position: "fixed",
- // FIXME replace 96px with actual header size
- top: 96,
+ top: HEADER_HEIGHT,
width: "100%",
- height: "calc(100vh - 96px)",
+ height: `calc(100vh - ${HEADER_HEIGHT}px)`,
display: "grid",
gridTemplateColumns: `${DRAWER_WIDTH}px minmax(22rem, 1fr)`,
gridTemplateRows: "auto minmax(0, 1fr)",
diff --git a/app/db/config.ts b/app/db/config.ts
index eff7467d2..24ce8cbb1 100644
--- a/app/db/config.ts
+++ b/app/db/config.ts
@@ -4,7 +4,7 @@
import { Config, Prisma, User } from "@prisma/client";
-import { ChartConfig } from "@/configurator";
+import { ChartConfig, ConfiguratorStatePublished } from "@/configurator";
import { migrateConfiguratorState } from "@/utils/chart-config/versioning";
import { createChartId } from "../utils/create-chart-id";
@@ -57,13 +57,16 @@ const ensureFiltersOrder = (chartConfig: ChartConfig) => {
};
};
-type ChartJsonConfig = {
- dataSet: string;
- chartConfig: Prisma.JsonObject;
-};
-
-const parseDbConfig = (d: Config) => {
- const data = d.data as ChartJsonConfig;
+const parseDbConfig = (
+ d: Config
+): {
+ id: number;
+ key: string;
+ data: ConfiguratorStatePublished;
+ created_at: Date;
+ user_id: number | null;
+} => {
+ const data = d.data as ConfiguratorStatePublished;
const migratedData = migrateConfiguratorState(data);
return {
@@ -84,7 +87,7 @@ const parseDbConfig = (d: Config) => {
export const getConfig = async (key: string) => {
const config = await prisma.config.findFirst({
where: {
- key: key,
+ key,
},
});
diff --git a/app/components/login-menu.tsx b/app/login/components/login-menu.tsx
similarity index 52%
rename from app/components/login-menu.tsx
rename to app/login/components/login-menu.tsx
index 32b7bfb98..0611341d9 100644
--- a/app/components/login-menu.tsx
+++ b/app/login/components/login-menu.tsx
@@ -1,5 +1,5 @@
import { Box, Button, Typography } from "@mui/material";
-import { getProviders, signIn, signOut, useSession } from "next-auth/react";
+import { getProviders, signIn, useSession } from "next-auth/react";
import Link from "next/link";
import { useEffect, useState } from "react";
@@ -12,63 +12,49 @@ const useProviders = () => {
status: "loading",
data: undefined as Providers | undefined,
});
+
useEffect(() => {
const run = async () => {
const providers = await getProviders();
setState({ status: "loaded", data: providers });
};
+
run();
}, []);
+
return state;
};
-function LoginMenu() {
+export const LoginMenu = () => {
const { data: session, status: sessionStatus } = useSession();
const { data: providers, status: providersStatus } = useProviders();
+
if (sessionStatus === "loading" || providersStatus === "loading") {
return null;
}
+
if (!providers || !Object.keys(providers).length) {
return null;
}
+
return (
{session ? (
- <>
-
- Signed in as{" "}
-
- {session.user?.name}
- {" "}
- {" - "}
-
-
- >
+
+
+ {session.user?.name}
+ {" "}
+
) : (
- <>
-
- Not signed in
- {" - "}
-
-
- >
+
)}
);
-}
-
-export default LoginMenu;
+};
diff --git a/app/login/components/profile-content-tabs.tsx b/app/login/components/profile-content-tabs.tsx
new file mode 100644
index 000000000..2995c1772
--- /dev/null
+++ b/app/login/components/profile-content-tabs.tsx
@@ -0,0 +1,88 @@
+import { TabContext, TabList, TabPanel } from "@mui/lab";
+import { Box, Tab, Theme, Typography } from "@mui/material";
+import { makeStyles } from "@mui/styles";
+import clsx from "clsx";
+import React from "react";
+
+import { ParsedConfig } from "@/db/config";
+import { ProfileVisualizationsTable } from "@/login/components/profile-tables";
+import { useRootStyles } from "@/login/utils";
+import useEvent from "@/utils/use-event";
+
+const useStyles = makeStyles((theme) => ({
+ section: {
+ marginTop: theme.spacing(6),
+ },
+ tabList: {
+ minHeight: "fit-content",
+
+ "& .MuiTabs-flexContainer": {
+ height: "fit-content",
+ },
+ },
+ tabPanel: {
+ padding: 0,
+ },
+ tabPanelContent: {
+ padding: theme.spacing(6),
+ },
+ tab: {
+ height: 48,
+ minHeight: 0,
+ padding: "0 1rem",
+ textTransform: "none",
+ },
+}));
+
+type ProfileContentTabsProps = {
+ userConfigs: ParsedConfig[];
+};
+
+export const ProfileContentTabs = (props: ProfileContentTabsProps) => {
+ const { userConfigs } = props;
+ const [value, setValue] = React.useState("Home");
+ const handleChange = useEvent((_: React.SyntheticEvent, v: string) => {
+ setValue(v);
+ });
+ const rootClasses = useRootStyles();
+ const classes = useStyles();
+
+ return (
+
+
+
+
+
+ {["Home", "My visualizations", "My favorite datasets"].map(
+ (d) => (
+
+ )
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ My favorite datasets
+
+
+
+ );
+};
diff --git a/app/login/components/profile-header.tsx b/app/login/components/profile-header.tsx
new file mode 100644
index 000000000..5d9c6e8ac
--- /dev/null
+++ b/app/login/components/profile-header.tsx
@@ -0,0 +1,54 @@
+import { Box, Button, Link, Theme, Typography } from "@mui/material";
+import { makeStyles } from "@mui/styles";
+import { User } from "@prisma/client";
+import clsx from "clsx";
+import { signOut } from "next-auth/react";
+
+import { useRootStyles } from "@/login/utils";
+
+const useStyles = makeStyles((theme) => ({
+ section: {
+ paddingTop: theme.spacing(6),
+ backgroundColor: theme.palette.muted.main,
+ },
+ topRow: {
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "space-between",
+ },
+ browseButton: {
+ width: "fit-content",
+ },
+}));
+
+type ProfileHeaderProps = {
+ user: User;
+};
+
+export const ProfileHeader = (props: ProfileHeaderProps) => {
+ const { user } = props;
+ const rootClasses = useRootStyles();
+ const classes = useStyles();
+
+ return (
+
+
+
+ {user.name}
+
+
+
+
+
+ );
+};
diff --git a/app/login/components/profile-tables.tsx b/app/login/components/profile-tables.tsx
new file mode 100644
index 000000000..aa49aa0b8
--- /dev/null
+++ b/app/login/components/profile-tables.tsx
@@ -0,0 +1,100 @@
+import {
+ Box,
+ Link,
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableRow,
+ Typography,
+} from "@mui/material";
+import NextLink from "next/link";
+
+import { ParsedConfig } from "@/db/config";
+import { useDataCubeMetadataQuery } from "@/graphql/query-hooks";
+import { Icon } from "@/icons";
+import { useRootStyles } from "@/login/utils";
+import { useLocale } from "@/src";
+
+type ProfileVisualizationsTableProps = {
+ userConfigs: ParsedConfig[];
+};
+
+export const ProfileVisualizationsTable = (
+ props: ProfileVisualizationsTableProps
+) => {
+ const { userConfigs } = props;
+ const rootClasses = useRootStyles();
+
+ return (
+
+ My visualizations
+ {userConfigs.length > 0 ? (
+
+
+ Chart type
+ Name
+ Dataset
+ Actions
+
+
+ {userConfigs.map((d) => (
+
+ ))}
+
+
+ ) : (
+
+ No charts yet,{" "}
+
+ create one
+
+ .
+
+ )}
+
+ );
+};
+
+type RowProps = {
+ config: ParsedConfig;
+};
+
+const Row = (props: RowProps) => {
+ const { config } = props;
+ const locale = useLocale();
+ const [{ data }] = useDataCubeMetadataQuery({
+ variables: {
+ iri: config.data.dataSet,
+ sourceType: config.data.dataSource.type,
+ sourceUrl: config.data.dataSource.url,
+ locale,
+ },
+ });
+
+ return (
+
+
+ {config.data.chartConfigs.length > 1
+ ? "multi"
+ : config.data.chartConfigs[0].chartType}
+
+ {config.data.meta.title[locale]}
+ {data?.dataCubeByIri?.title ?? ""}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/app/login/utils.ts b/app/login/utils.ts
new file mode 100644
index 000000000..aa5389ce9
--- /dev/null
+++ b/app/login/utils.ts
@@ -0,0 +1,21 @@
+import { Theme } from "@mui/material";
+import { makeStyles } from "@mui/styles";
+
+import { HEADER_HEIGHT } from "@/components/header";
+
+export const useRootStyles = makeStyles((theme) => ({
+ root: {
+ marginTop: `${HEADER_HEIGHT}px`,
+ backgroundColor: theme.palette.muted.main,
+ },
+ section: {
+ display: "flex",
+ flexDirection: "column",
+ padding: `0 ${theme.spacing(6)}`,
+ },
+ sectionContent: {
+ width: "100%",
+ maxWidth: 1400,
+ margin: "0 auto",
+ },
+}));
diff --git a/app/pages/profile.tsx b/app/pages/profile.tsx
index b79fc7b72..73566df6a 100644
--- a/app/pages/profile.tsx
+++ b/app/pages/profile.tsx
@@ -1,25 +1,15 @@
-import {
- Box,
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableRow,
- Typography,
-} from "@mui/material";
+import { Box } from "@mui/material";
import { User } from "@prisma/client";
import { GetServerSideProps } from "next";
import { getServerSession } from "next-auth";
-import Link from "next/link";
import { AppLayout } from "@/components/layout";
-import {
- PanelLayout,
- PanelMiddleWrapper,
-} from "@/configurator/components/layout";
import { ParsedConfig, getUserConfigs } from "@/db/config";
import { Serialized, deserializeProps, serializeProps } from "@/db/serialize";
import { findBySub } from "@/db/user";
+import { ProfileContentTabs } from "@/login/components/profile-content-tabs";
+import { ProfileHeader } from "@/login/components/profile-header";
+import { useRootStyles } from "@/login/utils";
import { nextAuthOptions } from "./api/auth/[...nextauth]";
@@ -31,11 +21,14 @@ type PageProps = {
export const getServerSideProps: GetServerSideProps = async (
ctx
) => {
- const session = await getServerSession(ctx.req, ctx.res, nextAuthOptions);
+ const { req, res } = ctx;
+ const session = await getServerSession(req, res, nextAuthOptions);
const userSub = session?.user.sub;
+
if (userSub) {
const user = await findBySub(userSub);
const userConfigs = await getUserConfigs(user.id);
+
return {
props: serializeProps({
user,
@@ -54,51 +47,14 @@ export const getServerSideProps: GetServerSideProps = async (
const ProfilePage = (props: Serialized) => {
const { user, userConfigs } = deserializeProps(props);
+ const rootClasses = useRootStyles();
return (
-
-
-
- Hello {user.name} 👋
-
- Charts
-
- {userConfigs.length > 0 ? (
-
-
- Type
- Title
- Link
-
-
- {userConfigs.map((uc) => {
- return (
-
- {uc.data.chartConfig.chartType}
- {uc.data.meta.title.en}
-
-
- See chart
-
-
-
- );
- })}
-
-
- ) : (
-
- No charts yet,{" "}
-
- create one
-
- .
-
- )}
-
-
-
+
+
+
+
);
};