Skip to content

Commit

Permalink
feat: redux persistence
Browse files Browse the repository at this point in the history
  • Loading branch information
arbisoft-qaisarirfan committed Feb 10, 2025
1 parent d9a5265 commit 284e39b
Show file tree
Hide file tree
Showing 18 changed files with 6,986 additions and 6,745 deletions.
2 changes: 2 additions & 0 deletions example.env.local
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
NEXT_PUBLIC_BASE_URL=http://localhost:8000
NEXT_PUBLIC_CLIENT_ID=
13,386 changes: 6,712 additions & 6,674 deletions package-lock.json

Large diffs are not rendered by default.

19 changes: 11 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "reactjs-boilerplate",
"name": "session-portal",
"version": "1.1.0",
"private": true,
"scripts": {
Expand Down Expand Up @@ -31,15 +31,10 @@
"@reduxjs/toolkit": "^2.5.0",
"@tanstack/react-query": "^5.0.0",
"@tanstack/react-query-devtools": "^5.0.0",
"@types/node": "20.14.10",
"@types/react": "18.3.11",
"@types/react-dom": "18.3.1",
"@vidstack/react": "^1.12.12",
"accept-language": "^3.0.18",
"date-fns": "^3.6.0",
"dotenv": "^16.3.1",
"eslint": "8.57.1",
"eslint-config-next": "14.2.15",
"i18next": "23.16.2",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-resources-to-backend": "^1.1.4",
Expand All @@ -56,7 +51,8 @@
"react-i18next": "^15.0.0",
"react-redux": "^9.2.0",
"react-virtuoso": "4.12.0",
"typescript": "5.5.4",
"redux-persist": "^6.0.0",
"redux-persist-transform-filter": "^0.0.22",
"yup": "^1.2.0"
},
"devDependencies": {
Expand All @@ -80,10 +76,16 @@
"@types/imap": "^0.8.40",
"@types/jest": "^29.5.14",
"@types/js-cookie": "^3.0.3",
"@types/lodash": "^4.17.15",
"@types/mailparser": "^3.4.4",
"@types/node": "20.14.10",
"@types/react": "18.3.11",
"@types/react-dom": "18.3.1",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"babel-jest": "^29.7.0",
"eslint": "8.57.1",
"eslint-config-next": "14.2.15",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.2",
"eslint-plugin-storybook": "^0.11.0",
Expand All @@ -96,7 +98,8 @@
"prettier": "^3.2.5",
"release-it": "^17.1.1",
"storybook": "^8.1.10",
"ts-node": "^10.9.2"
"ts-node": "^10.9.2",
"typescript": "^5.7.3"
},
"release-it": {
"git": {
Expand Down
30 changes: 30 additions & 0 deletions src/app/[language]/videos/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Typography from "@mui/material/Typography";
import type { Metadata } from "next";
// eslint-disable-next-line no-restricted-imports
import Link from "next/link";

import MainLayoutContainer from "@/components/containers/MainLayoutContainer";
import { getServerTranslation } from "@/services/i18n";

type Props = {
params: { language: string };
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { t } = await getServerTranslation(params.language, "common");

return {
title: t("videos"),
};
}

export default function Videos() {
return (
<MainLayoutContainer>
<Typography variant="h3" color="primary" width="100%">
Videos
</Typography>
<Link href="/videos/asdf">Video Detail</Link>
</MainLayoutContainer>
);
}
22 changes: 20 additions & 2 deletions src/components/Navbar/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,20 @@ import MenuItem from "@mui/material/MenuItem";
import Toolbar from "@mui/material/Toolbar";
import Tooltip from "@mui/material/Tooltip";
import Typography from "@mui/material/Typography";
import { useDispatch, useSelector } from "react-redux";

import { selectUserInfo } from "@/redux/login/selectors";
import { loginActions } from "@/redux/login/slice";
import { persistor } from "@/redux/store/configureStore";

import { Logo, Search, SearchIconWrapper, StyledInputBase } from "./styled";

const settings = ["Profile", "Account", "Dashboard", "Logout"];
const settings = ["Profile", "Account", "Dashboard"];

function Navbar() {
const dispatch = useDispatch();
const userInfo = useSelector(selectUserInfo);

const [anchorElUser, setAnchorElUser] = useState<null | HTMLElement>(null);

const handleOpenUserMenu = (event: React.MouseEvent<HTMLElement>) => {
Expand All @@ -28,6 +36,13 @@ function Navbar() {
setAnchorElUser(null);
};

const handleLogout = async () => {
dispatch(loginActions.logout());
await persistor.purge();
persistor.persist();
handleCloseUserMenu();
};

return (
<AppBar position="static">
<Container maxWidth={false}>
Expand All @@ -50,7 +65,7 @@ function Navbar() {
<Box sx={{ flexGrow: 0, width: 240, display: "flex", justifyContent: "flex-end" }}>
<Tooltip title="Open settings">
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
<Avatar variant="rounded" alt="Remy Sharp" />
<Avatar variant="rounded" alt={userInfo.full_name ?? ""} src={userInfo.avatar ?? ""} />
</IconButton>
</Tooltip>
<Menu
Expand All @@ -72,6 +87,9 @@ function Navbar() {
<Typography>{setting}</Typography>
</MenuItem>
))}
<MenuItem onClick={handleLogout}>
<Typography>Logout</Typography>
</MenuItem>
</Menu>
</Box>
</Toolbar>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import React, { isValidElement, ReactNode } from "react";

import Navbar from "@/components/Navbar";
import useAuth from "@/hooks/useAuth";

import { MainContainer, LeftSidebar, RightSidebar, ContentContainer } from "./styled";

Expand All @@ -12,6 +13,8 @@ type TMainLayoutContainer = {
};

const MainLayoutContainer = ({ children, rightSidebar }: TMainLayoutContainer) => {
useAuth();

return (
<>
<Navbar />
Expand Down
35 changes: 24 additions & 11 deletions src/components/theme/theme-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useMemo, PropsWithChildren } from "react";

import GlobalStyles from "@mui/material/GlobalStyles";
import { createTheme, ThemeProvider as MuiThemeProvider, Shadows, useTheme } from "@mui/material/styles";
import { Roboto_Condensed } from "next/font/google";

Expand All @@ -14,23 +15,21 @@ function ThemeProvider(props: PropsWithChildren<{}>) {
const theme = useMemo(
() =>
createTheme({
palette: {
primary: {
main: "#18465e", // TODO: will change colors when colors are provided
contrastText: "#fff",
},
secondary: {
main: "#eb6009", // TODO: will change colors are provided
contrastText: "#fff",
},
},
cssVariables: {
colorSchemeSelector: "class",
},
colorSchemes: { light: true, dark: true },
shadows: [...defaultTheme.shadows].map(() => "none") as Shadows,
palette: {
...defaultTheme.palette,
primary: {
main: "#18465e", // TODO: will change colors when colors are provided
contrastText: colors.white,
},
secondary: {
main: "#eb6009", // TODO: will change colors are provided
contrastText: colors.white,
},
colors: {
...colors,
},
Expand All @@ -42,7 +41,21 @@ function ThemeProvider(props: PropsWithChildren<{}>) {
[defaultTheme.shadows]
);

return <MuiThemeProvider theme={theme}>{props.children}</MuiThemeProvider>;
return (
<MuiThemeProvider theme={theme}>
<GlobalStyles
styles={{
"html,body,#__next": {
backgroundColor: "unset",
fontFamily: theme.typography.fontFamily,
height: "100%",
width: "100%",
},
}}
/>
{props.children}
</MuiThemeProvider>
);
}

export default ThemeProvider;
17 changes: 3 additions & 14 deletions src/features/HomePage/homePage.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,14 @@
"use client";

import { FC, useEffect } from "react";
import { FC } from "react";

import Box from "@mui/material/Box";
import Skeleton from "@mui/material/Skeleton";
import { useRouter } from "next/navigation";

import useLanguage from "@/services/i18n/use-language";
import useAuth from "@/hooks/useAuth";

const HomePage: FC = () => {
const router = useRouter();
const language = useLanguage();

useEffect(() => {
const token = localStorage.getItem("access_token");
if (!token) {
router.push(`/${language}/login/`);
} else {
router.push(`/${language}/videos/`);
}
}, [language, router]);
useAuth();

return (
<Box width="100vw" height="100vh" display="flex" justifyContent="center" alignItems="center">
Expand Down
34 changes: 19 additions & 15 deletions src/features/LoginPage/loginPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"use client";
import { useEffect, useState } from "react";
import { useState } from "react";

import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
Expand All @@ -11,31 +11,34 @@ import Image from "next/image";
import { useRouter } from "next/navigation";

import AlertModal from "@/components/AlertModal";
import { useProposalsMutation } from "@/redux/Login/loginSlice";
import useAuth from "@/hooks/useAuth";
import { useLoginMutation } from "@/redux/login/apiSlice";
import useLanguage from "@/services/i18n/use-language";

import { LoginButtonContainer, LoginContainer, LoginSubContainer } from "./styled";

export default function LoginPage() {
const [error, setError] = useState<string | null>(null);
useAuth();

const router = useRouter();
const language = useLanguage();
const theme = useTheme();

const [login] = useProposalsMutation();
const [error, setError] = useState<string | null>(null);
const [isLogin, setIsLogin] = useState(false);

const [login] = useLoginMutation();

const onSuccess = async (credentialResponse: CredentialResponse) => {
setIsLogin(false);
if ("access_token" in credentialResponse) {
const response = await login({
auth_token: credentialResponse.access_token as string,
});
const errorState = response?.error as FetchBaseQueryError;

if (response.data) {
localStorage.setItem("access_token", response.data.access ?? "");
localStorage.setItem("refresh_token", response.data.refresh ?? "");
router.replace(`/${language}/`);
router.replace(`/${language}/videos`);
} else if (errorState) {
const errorMessage = errorState.data as string[];
setError(errorMessage[0]);
Expand All @@ -47,6 +50,7 @@ export default function LoginPage() {
};

const onError = () => {
setIsLogin(false);
setError("Authentication Error: Google login failed. Please try again.");
};

Expand All @@ -55,19 +59,19 @@ export default function LoginPage() {
onError: onError,
});

useEffect(() => {
const token = localStorage.getItem("access_token");
if (token) {
router.push(`/${language}/`);
}
}, [router, language]);

return (
<LoginContainer>
<LoginSubContainer>
<Image height={33} width={131} src="/assets/images/arbisoft-logo.png" alt="arbisoft-logo" />
<LoginButtonContainer>
<Button className="login-button" onClick={() => googleLoginHandler()}>
<Button
disabled={isLogin}
className="login-button"
onClick={() => {
setIsLogin(true);
googleLoginHandler();
}}
>
<Box className="button-content">
<Image height={20} width={20} src="/assets/svgs/google.svg" alt="google-logo" />
<Typography color={theme.palette.colors.gray}>Sign in with Google</Typography>
Expand Down
26 changes: 26 additions & 0 deletions src/hooks/useAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useEffect } from "react";

import { useRouter, usePathname } from "next/navigation";
import { useSelector } from "react-redux";

import { selectAccessToken } from "@/redux/login/selectors";
import useLanguage from "@/services/i18n/use-language";

const useAuth = () => {
const language = useLanguage();
const router = useRouter();
const pathname = usePathname();
const token = useSelector(selectAccessToken);

useEffect(() => {
const isRootOrLoginPage = pathname === `/${language}` || pathname === `/${language}/login`;

if (token && isRootOrLoginPage) {
router.push(`/${language}/videos`);
} else if (!token) {
router.push(`/${language}/login`);
}
}, [router, language, token, pathname]);
};

export default useAuth;
12 changes: 6 additions & 6 deletions src/models/Auth/auth.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
export type LoginResponse = {
access: string;
refresh: string;
access: string | null;
refresh: string | null;
user_info: {
avatar: string;
first_name: string;
full_name: string;
last_name: string;
avatar: string | null;
first_name: string | null;
full_name: string | null;
last_name: string | null;
};
};

Expand Down
Loading

0 comments on commit 284e39b

Please sign in to comment.