diff --git a/frontend/src/components/Meta.tsx b/frontend/src/components/Meta.tsx
index c755ac27..da5c8907 100644
--- a/frontend/src/components/Meta.tsx
+++ b/frontend/src/components/Meta.tsx
@@ -7,18 +7,20 @@ const Meta = ({
title: string;
description?: string;
}) => {
+ const metaTitle = `${title} - Pingvin Share`;
+
return (
- {/* TODO: Doesn't work because script get only executed on client side */}
- {title} - Pingvin Share
-
+ {metaTitle}
+
-
+
+
);
diff --git a/frontend/src/components/account/ThemeSwitcher.tsx b/frontend/src/components/account/ThemeSwitcher.tsx
index 9bd77828..75eea921 100644
--- a/frontend/src/components/account/ThemeSwitcher.tsx
+++ b/frontend/src/components/account/ThemeSwitcher.tsx
@@ -18,7 +18,6 @@ const ThemeSwitcher = () => {
);
const { toggleColorScheme } = useMantineColorScheme();
const systemColorScheme = useColorScheme();
-
return (
{
})
.catch(toast.axiosError);
}
+ config.refresh();
};
useEffect(() => {
diff --git a/frontend/src/components/auth/SignInForm.tsx b/frontend/src/components/auth/SignInForm.tsx
index d0d483dd..9d1fb494 100644
--- a/frontend/src/components/auth/SignInForm.tsx
+++ b/frontend/src/components/auth/SignInForm.tsx
@@ -18,13 +18,12 @@ import * as yup from "yup";
import useConfig from "../../hooks/config.hook";
import useUser from "../../hooks/user.hook";
import authService from "../../services/auth.service";
-import userService from "../../services/user.service";
import toast from "../../utils/toast.util";
const SignInForm = ({ redirectPath }: { redirectPath: string }) => {
const config = useConfig();
const router = useRouter();
- const { setUser } = useUser();
+ const { refreshUser } = useUser();
const [showTotp, setShowTotp] = React.useState(false);
const [loginToken, setLoginToken] = React.useState("");
@@ -64,7 +63,7 @@ const SignInForm = ({ redirectPath }: { redirectPath: string }) => {
});
setLoginToken(response.data["loginToken"]);
} else {
- setUser(await userService.getCurrentUser());
+ await refreshUser();
router.replace(redirectPath);
}
})
@@ -74,7 +73,10 @@ const SignInForm = ({ redirectPath }: { redirectPath: string }) => {
const signInTotp = (email: string, password: string, totp: string) => {
authService
.signInTotp(email, password, totp, loginToken)
- .then(() => window.location.replace("/"))
+ .then(async () => {
+ await refreshUser();
+ router.replace(redirectPath);
+ })
.catch((error) => {
if (error?.response?.data?.message == "Login token expired") {
toast.error("Login token expired");
diff --git a/frontend/src/components/auth/SignUpForm.tsx b/frontend/src/components/auth/SignUpForm.tsx
index 02bd6364..f1fe3306 100644
--- a/frontend/src/components/auth/SignUpForm.tsx
+++ b/frontend/src/components/auth/SignUpForm.tsx
@@ -15,13 +15,12 @@ import * as yup from "yup";
import useConfig from "../../hooks/config.hook";
import useUser from "../../hooks/user.hook";
import authService from "../../services/auth.service";
-import userService from "../../services/user.service";
import toast from "../../utils/toast.util";
const SignUpForm = () => {
const config = useConfig();
const router = useRouter();
- const { setUser } = useUser();
+ const { refreshUser } = useUser();
const validationSchema = yup.object().shape({
email: yup.string().email().required(),
@@ -42,8 +41,8 @@ const SignUpForm = () => {
await authService
.signUp(email, username, password)
.then(async () => {
- setUser(await userService.getCurrentUser());
- router.replace("/");
+ await refreshUser();
+ router.replace("/upload");
})
.catch(toast.axiosError);
};
diff --git a/frontend/src/components/navBar/NavBar.tsx b/frontend/src/components/navBar/NavBar.tsx
index d80dd547..020ff7a3 100644
--- a/frontend/src/components/navBar/NavBar.tsx
+++ b/frontend/src/components/navBar/NavBar.tsx
@@ -1,5 +1,4 @@
import {
- ActionIcon,
Box,
Burger,
Container,
@@ -14,7 +13,6 @@ import {
import { useDisclosure } from "@mantine/hooks";
import Link from "next/link";
import { ReactNode, useEffect, useState } from "react";
-import { TbPlus } from "react-icons/tb";
import useConfig from "../../hooks/config.hook";
import useUser from "../../hooks/user.hook";
import Logo from "../Logo";
@@ -172,7 +170,9 @@ const NavBar = () => {
href={link.link ?? ""}
onClick={() => toggleOpened.toggle()}
className={cx(classes.link, {
- [classes.linkActive]: window.location.pathname == link.link,
+ [classes.linkActive]:
+ typeof window != "undefined" &&
+ window.location.pathname == link.link,
})}
>
{link.label}
diff --git a/frontend/src/hooks/config.hook.ts b/frontend/src/hooks/config.hook.ts
index 8f3c271c..24ab4894 100644
--- a/frontend/src/hooks/config.hook.ts
+++ b/frontend/src/hooks/config.hook.ts
@@ -1,13 +1,17 @@
import { createContext, useContext } from "react";
import configService from "../services/config.service";
-import Config from "../types/config.type";
+import { ConfigHook } from "../types/config.type";
-export const ConfigContext = createContext(null);
+export const ConfigContext = createContext({
+ configVariables: [],
+ refresh: () => {},
+});
const useConfig = () => {
- const configVariables = useContext(ConfigContext) as Config[];
+ const configContext = useContext(ConfigContext);
return {
- get: (key: string) => configService.get(key, configVariables),
+ get: (key: string) => configService.get(key, configContext.configVariables),
+ refresh: () => configContext.refresh(),
};
};
diff --git a/frontend/src/hooks/user.hook.ts b/frontend/src/hooks/user.hook.ts
index 08be50c0..47dd2c87 100644
--- a/frontend/src/hooks/user.hook.ts
+++ b/frontend/src/hooks/user.hook.ts
@@ -3,7 +3,7 @@ import { UserHook } from "../types/user.type";
export const UserContext = createContext({
user: null,
- setUser: () => {},
+ refreshUser: async () => null,
});
const useUser = () => {
diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx
index 17d0df9f..8a4c551c 100644
--- a/frontend/src/pages/_app.tsx
+++ b/frontend/src/pages/_app.tsx
@@ -2,12 +2,14 @@ import {
ColorScheme,
ColorSchemeProvider,
Container,
- LoadingOverlay,
MantineProvider,
} from "@mantine/core";
import { useColorScheme } from "@mantine/hooks";
import { ModalsProvider } from "@mantine/modals";
import { NotificationsProvider } from "@mantine/notifications";
+import axios from "axios";
+import { getCookie, setCookie } from "cookies-next";
+import { GetServerSidePropsContext } from "next";
import type { AppProps } from "next/app";
import { useEffect, useState } from "react";
import Header from "../components/navBar/NavBar";
@@ -21,38 +23,38 @@ import GlobalStyle from "../styles/global.style";
import globalStyle from "../styles/mantine.style";
import Config from "../types/config.type";
import { CurrentUser } from "../types/user.type";
-import { GlobalLoadingContext } from "../utils/loading.util";
function App({ Component, pageProps }: AppProps) {
- const systemTheme = useColorScheme();
-
+ const systemTheme = useColorScheme(pageProps.colorScheme);
+ const [colorScheme, setColorScheme] = useState(systemTheme);
const preferences = usePreferences();
- const [colorScheme, setColorScheme] = useState("light");
- const [isLoading, setIsLoading] = useState(true);
- const [user, setUser] = useState(null);
- const [configVariables, setConfigVariables] = useState(null);
- const getInitalData = async () => {
- setIsLoading(true);
- setConfigVariables(await configService.list());
- await authService.refreshAccessToken();
- setUser(await userService.getCurrentUser());
- setIsLoading(false);
- };
+ const [user, setUser] = useState(pageProps.user);
+
+ const [configVariables, setConfigVariables] = useState(
+ pageProps.configVariables
+ );
useEffect(() => {
setInterval(async () => await authService.refreshAccessToken(), 30 * 1000);
- getInitalData();
}, []);
useEffect(() => {
- setColorScheme(
+ const colorScheme =
preferences.get("colorScheme") == "system"
? systemTheme
- : preferences.get("colorScheme")
- );
+ : preferences.get("colorScheme");
+
+ toggleColorScheme(colorScheme);
}, [systemTheme]);
+ const toggleColorScheme = (value: ColorScheme) => {
+ setColorScheme(value ?? "light");
+ setCookie("mantine-color-scheme", value ?? "light", {
+ sameSite: "lax",
+ });
+ };
+
return (
setColorScheme(value ?? "light")}
+ toggleColorScheme={toggleColorScheme}
>
-
- {isLoading ? (
-
- ) : (
-
-
-
-
-
-
-
-
-
- )}
-
+ {
+ setConfigVariables(await configService.list());
+ },
+ }}
+ >
+ {
+ const user = await userService.getCurrentUser();
+ setUser(user);
+ return user;
+ },
+ }}
+ >
+
+
+
+
+
+
@@ -88,4 +99,33 @@ function App({ Component, pageProps }: AppProps) {
);
}
+// Fetch user and config variables on server side when the first request is made
+// These will get passed as a page prop to the App component and stored in the contexts
+App.getInitialProps = async ({ ctx }: { ctx: GetServerSidePropsContext }) => {
+ let pageProps: {
+ user?: CurrentUser;
+ configVariables?: Config[];
+ colorScheme: ColorScheme;
+ } = {
+ colorScheme:
+ (getCookie("mantine-color-scheme", ctx) as ColorScheme) ?? "light",
+ };
+
+ if (ctx.req) {
+ const cookieHeader = ctx.req.headers.cookie;
+
+ pageProps.user = await axios(`http://localhost:8080/api/users/me`, {
+ headers: { cookie: cookieHeader },
+ })
+ .then((res) => res.data)
+ .catch(() => null);
+
+ pageProps.configVariables = (
+ await axios(`http://localhost:8080/api/configs`)
+ ).data;
+ }
+
+ return { pageProps };
+};
+
export default App;
diff --git a/frontend/src/pages/account/index.tsx b/frontend/src/pages/account/index.tsx
index 796871a4..8819613a 100644
--- a/frontend/src/pages/account/index.tsx
+++ b/frontend/src/pages/account/index.tsx
@@ -24,7 +24,7 @@ import userService from "../../services/user.service";
import toast from "../../utils/toast.util";
const Account = () => {
- const { user, setUser } = useUser();
+ const { user, refreshUser } = useUser();
const modals = useModals();
const accountForm = useForm({
@@ -81,8 +81,6 @@ const Account = () => {
),
});
- const refreshUser = async () => setUser(await userService.getCurrentUser());
-
return (
<>
diff --git a/frontend/src/pages/account/shares.tsx b/frontend/src/pages/account/shares.tsx
index 834f4b5a..8a00807e 100644
--- a/frontend/src/pages/account/shares.tsx
+++ b/frontend/src/pages/account/shares.tsx
@@ -4,7 +4,6 @@ import {
Button,
Center,
Group,
- LoadingOverlay,
Space,
Stack,
Table,
@@ -18,6 +17,7 @@ import Link from "next/link";
import { useEffect, useState } from "react";
import { TbLink, TbTrash } from "react-icons/tb";
import showShareLinkModal from "../../components/account/showShareLinkModal";
+import CenterLoader from "../../components/core/CenterLoader";
import Meta from "../../components/Meta";
import useConfig from "../../hooks/config.hook";
import shareService from "../../services/share.service";
@@ -35,7 +35,8 @@ const MyShares = () => {
shareService.getMyShares().then((shares) => setShares(shares));
}, []);
- if (!shares) return ;
+ if (!shares) return ;
+
return (
<>
diff --git a/frontend/src/pages/auth/signIn.tsx b/frontend/src/pages/auth/signIn.tsx
index 317c05bc..08d28986 100644
--- a/frontend/src/pages/auth/signIn.tsx
+++ b/frontend/src/pages/auth/signIn.tsx
@@ -1,26 +1,41 @@
import { LoadingOverlay } from "@mantine/core";
+import { GetServerSidePropsContext } from "next";
import { useRouter } from "next/router";
+import { useEffect, useState } from "react";
import SignInForm from "../../components/auth/SignInForm";
import Meta from "../../components/Meta";
import useUser from "../../hooks/user.hook";
-const SignIn = () => {
- const { user } = useUser();
+export function getServerSideProps(context: GetServerSidePropsContext) {
+ return {
+ props: { redirectPath: context.query.redirect ?? null },
+ };
+}
+
+const SignIn = ({ redirectPath }: { redirectPath?: string }) => {
+ const { refreshUser } = useUser();
const router = useRouter();
- const redirectPath = (router.query.redirect as string) ?? "/upload";
+ const [isLoading, setIsLoading] = useState(redirectPath ? true : false);
// If the access token is expired, the middleware redirects to this page.
- // If the refresh token is still valid, the user will be redirected to the home page.
- if (user) {
- router.replace(redirectPath);
- return ;
- }
+ // If the refresh token is still valid, the user will be redirected to the last page.
+ useEffect(() => {
+ refreshUser().then((user) => {
+ if (user) {
+ router.replace(redirectPath ?? "/upload");
+ } else {
+ setIsLoading(false);
+ }
+ });
+ }, []);
+
+ if (isLoading) return ;
return (
<>
-
+
>
);
};
diff --git a/frontend/src/types/config.type.ts b/frontend/src/types/config.type.ts
index 44fa3ff9..12bc935e 100644
--- a/frontend/src/types/config.type.ts
+++ b/frontend/src/types/config.type.ts
@@ -29,4 +29,9 @@ export type AdminConfigGroupedByCategory = {
];
};
+export type ConfigHook = {
+ configVariables: Config[];
+ refresh: () => void;
+};
+
export default Config;
diff --git a/frontend/src/types/user.type.ts b/frontend/src/types/user.type.ts
index dcf8ad98..1761d4a2 100644
--- a/frontend/src/types/user.type.ts
+++ b/frontend/src/types/user.type.ts
@@ -29,7 +29,7 @@ export type CurrentUser = User & {};
export type UserHook = {
user: CurrentUser | null;
- setUser: (user: CurrentUser | null) => void;
+ refreshUser: () => Promise;
};
export default User;