This commit is contained in:
AmitChauhan63390
2024-12-07 22:20:17 +05:30
parent 36ebff45da
commit 5684243215
11 changed files with 508 additions and 228 deletions

View File

@@ -36,6 +36,9 @@
"fortawesome": "^0.0.1-security", "fortawesome": "^0.0.1-security",
"google-auth-library": "^9.14.1", "google-auth-library": "^9.14.1",
"googleapis": "^144.0.0", "googleapis": "^144.0.0",
"i18next": "^24.0.2",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^3.0.1",
"ioredis": "^5.4.1", "ioredis": "^5.4.1",
"joi": "^17.6.0", "joi": "^17.6.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
@@ -56,6 +59,7 @@
"react": "^18.0.0", "react": "^18.0.0",
"react-dom": "^18.0.0", "react-dom": "^18.0.0",
"react-highlight": "0.15.0", "react-highlight": "0.15.0",
"react-i18next": "^15.1.3",
"react-router-dom": "^6.26.1", "react-router-dom": "^6.26.1",
"react-simple-code-editor": "^0.11.2", "react-simple-code-editor": "^0.11.2",
"react-transition-group": "^4.4.2", "react-transition-group": "^4.4.2",

0
public/locales/ar.json Normal file
View File

13
public/locales/en.json Normal file
View File

@@ -0,0 +1,13 @@
{
"login": {
"title": "Welcome Back!",
"email": "Email",
"password": "Password",
"button": "Login",
"loading": "Loading",
"register_prompt": "Don't have an account?",
"register_link": "Register",
"welcome_notification": "Welcome to Maxun!",
"error_notification": "Login Failed. Please try again."
}
}

13
public/locales/es.json Normal file
View File

@@ -0,0 +1,13 @@
{
"app": {
"name": "Maxun",
"version": "beta"
},
"login": {
"title": "¡Bienvenido de nuevo!",
"email": "Correo electrónico",
"password": "Contraseña",
"button": "Iniciar sesión",
"register_prompt": "¿No tienes una cuenta? Regístrate"
}
}

13
public/locales/ja.json Normal file
View File

@@ -0,0 +1,13 @@
{
"app": {
"name": "Maxun",
"version": "beta"
},
"login": {
"title": "おかえりなさい!",
"email": "メールアドレス",
"password": "パスワード",
"button": "ログイン",
"register_prompt": "アカウントをお持ちでない方は、新規登録"
}
}

13
public/locales/zh.json Normal file
View File

@@ -0,0 +1,13 @@
{
"app": {
"name": "Maxun",
"version": "beta"
},
"login": {
"title": "欢迎回来!",
"email": "电子邮件",
"password": "密码",
"button": "登录",
"register_prompt": "没有账号?注册"
}
}

View File

@@ -1,8 +1,10 @@
import React from 'react'; import React from "react";
import { Routes, Route } from 'react-router-dom'; import { Routes, Route } from "react-router-dom";
import { ThemeProvider, createTheme } from "@mui/material/styles"; import { ThemeProvider, createTheme } from "@mui/material/styles";
import { GlobalInfoProvider } from "./context/globalInfo"; import { GlobalInfoProvider } from "./context/globalInfo";
import { PageWrapper } from "./pages/PageWrappper"; import { PageWrapper } from "./pages/PageWrappper";
import i18n from "./i18n";
const theme = createTheme({ const theme = createTheme({
palette: { palette: {
@@ -20,14 +22,14 @@ const theme = createTheme({
}, },
containedPrimary: { containedPrimary: {
// Styles for 'contained' variant with 'primary' color // Styles for 'contained' variant with 'primary' color
'&:hover': { "&:hover": {
backgroundColor: "#ff66d9", backgroundColor: "#ff66d9",
}, },
}, },
outlined: { outlined: {
// Apply white background for all 'outlined' variant buttons // Apply white background for all 'outlined' variant buttons
backgroundColor: "#ffffff", backgroundColor: "#ffffff",
'&:hover': { "&:hover": {
backgroundColor: "#f0f0f0", // Optional lighter background on hover backgroundColor: "#f0f0f0", // Optional lighter background on hover
}, },
}, },
@@ -36,7 +38,7 @@ const theme = createTheme({
MuiLink: { MuiLink: {
styleOverrides: { styleOverrides: {
root: { root: {
'&:hover': { "&:hover": {
color: "#ff00c3", color: "#ff00c3",
}, },
}, },
@@ -63,7 +65,7 @@ const theme = createTheme({
standardInfo: { standardInfo: {
backgroundColor: "#fce1f4", backgroundColor: "#fce1f4",
color: "#ff00c3", color: "#ff00c3",
'& .MuiAlert-icon': { "& .MuiAlert-icon": {
color: "#ff00c3", color: "#ff00c3",
}, },
}, },
@@ -72,7 +74,7 @@ const theme = createTheme({
MuiAlertTitle: { MuiAlertTitle: {
styleOverrides: { styleOverrides: {
root: { root: {
'& .MuiAlert-icon': { "& .MuiAlert-icon": {
color: "#ffffff", color: "#ffffff",
}, },
}, },
@@ -81,15 +83,16 @@ const theme = createTheme({
}, },
}); });
function App() { function App() {
return ( return (
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<GlobalInfoProvider> <GlobalInfoProvider>
<Routes> <Routes>
<Route path="/*" element={<PageWrapper />} /> <Route path="/*" element={<PageWrapper />} />
</Routes> </Routes>
</GlobalInfoProvider> </GlobalInfoProvider>
</ThemeProvider> </ThemeProvider>
); );
} }

View File

@@ -1,68 +1,103 @@
import React, { useState, useContext } from 'react'; import React, { useState, useContext } from "react";
import axios from 'axios'; import axios from "axios";
import styled from "styled-components"; import styled from "styled-components";
import { stopRecording } from "../../api/recording"; import { stopRecording } from "../../api/recording";
import { useGlobalInfoStore } from "../../context/globalInfo"; import { useGlobalInfoStore } from "../../context/globalInfo";
import { IconButton, Menu, MenuItem, Typography, Avatar, Chip, } from "@mui/material"; import { IconButton, Menu, MenuItem, Typography, Chip } from "@mui/material";
import { AccountCircle, Logout, Clear } from "@mui/icons-material"; import { AccountCircle, Logout, Clear } from "@mui/icons-material";
import { useNavigate } from 'react-router-dom'; import { useNavigate } from "react-router-dom";
import { AuthContext } from '../../context/auth'; import { AuthContext } from "../../context/auth";
import { SaveRecording } from '../molecules/SaveRecording'; import { SaveRecording } from "../molecules/SaveRecording";
import DiscordIcon from '../atoms/DiscordIcon'; import DiscordIcon from "../atoms/DiscordIcon";
import { apiUrl } from '../../apiConfig'; import { apiUrl } from "../../apiConfig";
import MaxunLogo from "../../assets/maxunlogo.png"; import MaxunLogo from "../../assets/maxunlogo.png";
import { useTranslation } from "react-i18next"; // Import useTranslation hook
interface NavBarProps { interface NavBarProps {
recordingName: string; recordingName: string;
isRecording: boolean; isRecording: boolean;
} }
export const NavBar: React.FC<NavBarProps> = ({ recordingName, isRecording }) => { export const NavBar: React.FC<NavBarProps> = ({
const { notify, browserId, setBrowserId, recordingUrl } = useGlobalInfoStore(); recordingName,
isRecording,
}) => {
const { notify, browserId, setBrowserId } = useGlobalInfoStore();
const { state, dispatch } = useContext(AuthContext); const { state, dispatch } = useContext(AuthContext);
const { user } = state; const { user } = state;
const navigate = useNavigate(); const navigate = useNavigate();
const { t, i18n } = useTranslation(); // Get translation function and i18n methods
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null); const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [langAnchorEl, setLangAnchorEl] = useState<null | HTMLElement>(null);
const handleMenuOpen = (event: React.MouseEvent<HTMLElement>) => { const handleMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget); setAnchorEl(event.currentTarget);
}; };
const handleLangMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
setLangAnchorEl(event.currentTarget);
};
const handleMenuClose = () => { const handleMenuClose = () => {
setAnchorEl(null); setAnchorEl(null);
setLangAnchorEl(null);
}; };
const logout = async () => { const logout = async () => {
dispatch({ type: 'LOGOUT' }); dispatch({ type: "LOGOUT" });
window.localStorage.removeItem('user'); window.localStorage.removeItem("user");
const { data } = await axios.get(`${apiUrl}/auth/logout`); const { data } = await axios.get(`${apiUrl}/auth/logout`);
notify('success', data.message); notify("success", data.message);
navigate('/login'); navigate("/login");
}; };
const goToMainMenu = async () => { const goToMainMenu = async () => {
if (browserId) { if (browserId) {
await stopRecording(browserId); await stopRecording(browserId);
notify('warning', 'Current Recording was terminated'); notify("warning", "Current Recording was terminated");
setBrowserId(null); setBrowserId(null);
} }
navigate('/'); navigate("/");
};
const changeLanguage = (lang: string) => {
i18n.changeLanguage(lang); // Change language dynamically
localStorage.setItem("language", lang); // Persist language to localStorage
}; };
return ( return (
<NavBarWrapper> <NavBarWrapper>
<div style={{ <div
display: 'flex', style={{
justifyContent: 'flex-start', display: "flex",
}}> justifyContent: "flex-start",
<img src={MaxunLogo} width={45} height={40} style={{ borderRadius: '5px', margin: '5px 0px 5px 15px' }} /> }}
<div style={{ padding: '11px' }}><ProjectName>Maxun</ProjectName></div> >
<Chip label="beta" color="primary" variant="outlined" sx={{ marginTop: '10px' }} /> <img
src={MaxunLogo}
width={45}
height={40}
style={{ borderRadius: "5px", margin: "5px 0px 5px 15px" }}
/>
<div style={{ padding: "11px" }}>
<ProjectName>Maxun</ProjectName>
</div> </div>
{ <Chip
user ? ( label="beta"
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }}> color="primary"
variant="outlined"
sx={{ marginTop: "10px" }}
/>
</div>
{user ? (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
}}
>
{!isRecording ? ( {!isRecording ? (
<> <>
<IconButton <IconButton
@@ -71,25 +106,35 @@ export const NavBar: React.FC<NavBarProps> = ({ recordingName, isRecording }) =>
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
sx={{ sx={{
display: 'flex', display: "flex",
alignItems: 'center', alignItems: "center",
borderRadius: '5px', borderRadius: "5px",
padding: '8px', padding: "8px",
marginRight: '30px', marginRight: "30px",
}} }}
> >
<DiscordIcon sx={{ marginRight: '5px' }} /> <DiscordIcon sx={{ marginRight: "5px" }} />
</IconButton> </IconButton>
<iframe src="https://ghbtns.com/github-btn.html?user=getmaxun&repo=maxun&type=star&count=true&size=large" frameBorder="0" scrolling="0" width="170" height="30" title="GitHub"></iframe> <iframe
<IconButton onClick={handleMenuOpen} sx={{ src="https://ghbtns.com/github-btn.html?user=getmaxun&repo=maxun&type=star&count=true&size=large"
display: 'flex', frameBorder="0"
alignItems: 'center', scrolling="0"
borderRadius: '5px', width="170"
padding: '8px', height="30"
marginRight: '10px', title="GitHub"
'&:hover': { backgroundColor: 'white', color: '#ff00c3' } ></iframe>
}}> <IconButton
<AccountCircle sx={{ marginRight: '5px' }} /> onClick={handleMenuOpen}
sx={{
display: "flex",
alignItems: "center",
borderRadius: "5px",
padding: "8px",
marginRight: "10px",
"&:hover": { backgroundColor: "white", color: "#ff00c3" },
}}
>
<AccountCircle sx={{ marginRight: "5px" }} />
<Typography variant="body1">{user.email}</Typography> <Typography variant="body1">{user.email}</Typography>
</IconButton> </IconButton>
<Menu <Menu
@@ -97,38 +142,181 @@ export const NavBar: React.FC<NavBarProps> = ({ recordingName, isRecording }) =>
open={Boolean(anchorEl)} open={Boolean(anchorEl)}
onClose={handleMenuClose} onClose={handleMenuClose}
anchorOrigin={{ anchorOrigin={{
vertical: 'bottom', vertical: "bottom",
horizontal: 'right', horizontal: "right",
}} }}
transformOrigin={{ transformOrigin={{
vertical: 'top', vertical: "top",
horizontal: 'right', horizontal: "right",
}} }}
> >
<MenuItem onClick={() => { handleMenuClose(); logout(); }}> <MenuItem
<Logout sx={{ marginRight: '5px' }} /> Logout onClick={() => {
handleMenuClose();
logout();
}}
>
<Logout sx={{ marginRight: "5px" }} /> {t("logout")}
</MenuItem> </MenuItem>
</Menu> </Menu>
{/* Language dropdown */}
</> </>
) : ( ) : (
<> <>
<IconButton onClick={goToMainMenu} sx={{ <IconButton
borderRadius: '5px', onClick={goToMainMenu}
padding: '8px', sx={{
background: 'red', borderRadius: "5px",
color: 'white', padding: "8px",
marginRight: '10px', background: "red",
'&:hover': { color: 'white', backgroundColor: 'red' } color: "white",
}}> marginRight: "10px",
<Clear sx={{ marginRight: '5px' }} /> "&:hover": { color: "white", backgroundColor: "red" },
Discard }}
>
<Clear sx={{ marginRight: "5px" }} />
{t("discard")}
</IconButton> </IconButton>
<SaveRecording fileName={recordingName} /> <SaveRecording fileName={recordingName} />
</> </>
)} )}
<IconButton
onClick={handleLangMenuOpen}
sx={{
display: "flex",
alignItems: "center",
borderRadius: "5px",
padding: "8px",
marginRight: "10px",
}}
>
<Typography variant="body1">{t("language")}</Typography>
</IconButton>
<Menu
anchorEl={langAnchorEl}
open={Boolean(langAnchorEl)}
onClose={handleMenuClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "right",
}}
transformOrigin={{
vertical: "top",
horizontal: "right",
}}
>
<MenuItem
onClick={() => {
changeLanguage("en");
handleMenuClose();
}}
>
English
</MenuItem>
<MenuItem
onClick={() => {
changeLanguage("es");
handleMenuClose();
}}
>
Español
</MenuItem>
<MenuItem
onClick={() => {
changeLanguage("ja");
handleMenuClose();
}}
>
</MenuItem>
<MenuItem
onClick={() => {
changeLanguage("ar");
handleMenuClose();
}}
>
العربية
</MenuItem>
<MenuItem
onClick={() => {
changeLanguage("zh");
handleMenuClose();
}}
>
</MenuItem>
</Menu>
</div> </div>
) : "" ) : (
} <><IconButton
onClick={handleLangMenuOpen}
sx={{
display: "flex",
alignItems: "center",
borderRadius: "5px",
padding: "8px",
marginRight: "10px",
}}
>
<Typography variant="body1">{t("language")}</Typography>
</IconButton>
<Menu
anchorEl={langAnchorEl}
open={Boolean(langAnchorEl)}
onClose={handleMenuClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "right",
}}
transformOrigin={{
vertical: "top",
horizontal: "right",
}}
>
<MenuItem
onClick={() => {
changeLanguage("en");
handleMenuClose();
}}
>
English
</MenuItem>
<MenuItem
onClick={() => {
changeLanguage("es");
handleMenuClose();
}}
>
Español
</MenuItem>
<MenuItem
onClick={() => {
changeLanguage("ja");
handleMenuClose();
}}
>
</MenuItem>
<MenuItem
onClick={() => {
changeLanguage("ar");
handleMenuClose();
}}
>
العربية
</MenuItem>
<MenuItem
onClick={() => {
changeLanguage("zh");
handleMenuClose();
}}
>
</MenuItem>
</Menu></>
)}
</NavBarWrapper> </NavBarWrapper>
); );
}; };

22
src/i18n.ts Normal file
View File

@@ -0,0 +1,22 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'en',
debug: import.meta.env.DEV,
supportedLngs: ['en', 'es', 'ja', 'zh', 'ar'],
interpolation: {
escapeValue: false, // React already escapes
},
backend: {
loadPath: '/locales/{{lng}}.json',
},
});
export default i18n;

View File

@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client';
import './index.css'; import './index.css';
import { BrowserRouter } from 'react-router-dom'; import { BrowserRouter } from 'react-router-dom';
import App from './App'; import App from './App';
import i18n from "./i18n"
const root = ReactDOM.createRoot( const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement document.getElementById('root') as HTMLElement

View File

@@ -5,8 +5,13 @@ import { AuthContext } from "../context/auth";
import { Box, Typography, TextField, Button, CircularProgress, Grid } from "@mui/material"; import { Box, Typography, TextField, Button, CircularProgress, Grid } from "@mui/material";
import { useGlobalInfoStore } from "../context/globalInfo"; import { useGlobalInfoStore } from "../context/globalInfo";
import { apiUrl } from "../apiConfig"; import { apiUrl } from "../apiConfig";
import { useTranslation } from 'react-i18next';
import i18n from '../i18n'; // Add this import
const Login = () => { const Login = () => {
const { t } = useTranslation();
console.log(i18n) // Add translation hook
console.log(t)
const [form, setForm] = useState({ const [form, setForm] = useState({
email: "", email: "",
password: "", password: "",
@@ -40,15 +45,20 @@ const Login = () => {
password, password,
}); });
dispatch({ type: "LOGIN", payload: data }); dispatch({ type: "LOGIN", payload: data });
notify("success", "Welcome to Maxun!"); notify("success", t('login.welcome_notification')); // Translated notification
window.localStorage.setItem("user", JSON.stringify(data)); window.localStorage.setItem("user", JSON.stringify(data));
navigate("/"); navigate("/");
} catch (err) { } catch (err) {
notify("error", "Login Failed. Please try again."); notify("error", t('login.error_notification')); // Translated error
setLoading(false); setLoading(false);
} }
}; };
// Language switcher function
const changeLanguage = (lng: string) => {
i18n.changeLanguage(lng);
};
return ( return (
<Box <Box
sx={{ sx={{
@@ -60,6 +70,7 @@ const Login = () => {
padding: 4, padding: 4,
}} }}
> >
{/* Language Switcher Buttons */}
<Box <Box
component="form" component="form"
@@ -79,11 +90,11 @@ const Login = () => {
> >
<img src="../src/assets/maxunlogo.png" alt="logo" height={55} width={60} style={{ marginBottom: 20, borderRadius: "20%", alignItems: "center" }} /> <img src="../src/assets/maxunlogo.png" alt="logo" height={55} width={60} style={{ marginBottom: 20, borderRadius: "20%", alignItems: "center" }} />
<Typography variant="h4" gutterBottom> <Typography variant="h4" gutterBottom>
Welcome Back! {t('login.title')}
</Typography> </Typography>
<TextField <TextField
fullWidth fullWidth
label="Email" label={t('login.email')}
name="email" name="email"
value={email} value={email}
onChange={handleChange} onChange={handleChange}
@@ -93,7 +104,7 @@ const Login = () => {
/> />
<TextField <TextField
fullWidth fullWidth
label="Password" label={t('login.password')}
name="password" name="password"
type="password" type="password"
value={password} value={password}
@@ -113,21 +124,20 @@ const Login = () => {
{loading ? ( {loading ? (
<> <>
<CircularProgress size={20} sx={{ mr: 2 }} /> <CircularProgress size={20} sx={{ mr: 2 }} />
Loading {t('login.loading')}
</> </>
) : ( ) : (
"Login" t('login.button')
)} )}
</Button> </Button>
<Typography variant="body2" align="center"> <Typography variant="body2" align="center">
Dont have an account?{" "} {t('login.register_prompt')}{" "}
<Link to="/register" style={{ textDecoration: "none", color: "#ff33cc" }}> <Link to="/register" style={{ textDecoration: "none", color: "#ff33cc" }}>
Register {t('login.register_link')}
</Link> </Link>
</Typography> </Typography>
</Box> </Box>
</Box> </Box>
); );
}; };