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",
"google-auth-library": "^9.14.1",
"googleapis": "^144.0.0",
"i18next": "^24.0.2",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^3.0.1",
"ioredis": "^5.4.1",
"joi": "^17.6.0",
"jsonwebtoken": "^9.0.2",
@@ -56,6 +59,7 @@
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-highlight": "0.15.0",
"react-i18next": "^15.1.3",
"react-router-dom": "^6.26.1",
"react-simple-code-editor": "^0.11.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 { Routes, Route } from 'react-router-dom';
import React from "react";
import { Routes, Route } from "react-router-dom";
import { ThemeProvider, createTheme } from "@mui/material/styles";
import { GlobalInfoProvider } from "./context/globalInfo";
import { PageWrapper } from "./pages/PageWrappper";
import i18n from "./i18n";
const theme = createTheme({
palette: {
@@ -20,14 +22,14 @@ const theme = createTheme({
},
containedPrimary: {
// Styles for 'contained' variant with 'primary' color
'&:hover': {
"&:hover": {
backgroundColor: "#ff66d9",
},
},
outlined: {
// Apply white background for all 'outlined' variant buttons
backgroundColor: "#ffffff",
'&:hover': {
"&:hover": {
backgroundColor: "#f0f0f0", // Optional lighter background on hover
},
},
@@ -36,7 +38,7 @@ const theme = createTheme({
MuiLink: {
styleOverrides: {
root: {
'&:hover': {
"&:hover": {
color: "#ff00c3",
},
},
@@ -63,7 +65,7 @@ const theme = createTheme({
standardInfo: {
backgroundColor: "#fce1f4",
color: "#ff00c3",
'& .MuiAlert-icon': {
"& .MuiAlert-icon": {
color: "#ff00c3",
},
},
@@ -72,7 +74,7 @@ const theme = createTheme({
MuiAlertTitle: {
styleOverrides: {
root: {
'& .MuiAlert-icon': {
"& .MuiAlert-icon": {
color: "#ffffff",
},
},
@@ -81,15 +83,16 @@ const theme = createTheme({
},
});
function App() {
return (
<ThemeProvider theme={theme}>
<GlobalInfoProvider>
<Routes>
<Route path="/*" element={<PageWrapper />} />
</Routes>
</GlobalInfoProvider>
<GlobalInfoProvider>
<Routes>
<Route path="/*" element={<PageWrapper />} />
</Routes>
</GlobalInfoProvider>
</ThemeProvider>
);
}

View File

@@ -1,134 +1,322 @@
import React, { useState, useContext } from 'react';
import axios from 'axios';
import React, { useState, useContext } from "react";
import axios from "axios";
import styled from "styled-components";
import { stopRecording } from "../../api/recording";
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 { useNavigate } from 'react-router-dom';
import { AuthContext } from '../../context/auth';
import { SaveRecording } from '../molecules/SaveRecording';
import DiscordIcon from '../atoms/DiscordIcon';
import { apiUrl } from '../../apiConfig';
import { useNavigate } from "react-router-dom";
import { AuthContext } from "../../context/auth";
import { SaveRecording } from "../molecules/SaveRecording";
import DiscordIcon from "../atoms/DiscordIcon";
import { apiUrl } from "../../apiConfig";
import MaxunLogo from "../../assets/maxunlogo.png";
import { useTranslation } from "react-i18next"; // Import useTranslation hook
interface NavBarProps {
recordingName: string;
isRecording: boolean;
}
export const NavBar: React.FC<NavBarProps> = ({ recordingName, isRecording }) => {
const { notify, browserId, setBrowserId, recordingUrl } = useGlobalInfoStore();
export const NavBar: React.FC<NavBarProps> = ({
recordingName,
isRecording,
}) => {
const { notify, browserId, setBrowserId } = useGlobalInfoStore();
const { state, dispatch } = useContext(AuthContext);
const { user } = state;
const navigate = useNavigate();
const { t, i18n } = useTranslation(); // Get translation function and i18n methods
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [langAnchorEl, setLangAnchorEl] = useState<null | HTMLElement>(null);
const handleMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const handleLangMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
setLangAnchorEl(event.currentTarget);
};
const handleMenuClose = () => {
setAnchorEl(null);
setLangAnchorEl(null);
};
const logout = async () => {
dispatch({ type: 'LOGOUT' });
window.localStorage.removeItem('user');
dispatch({ type: "LOGOUT" });
window.localStorage.removeItem("user");
const { data } = await axios.get(`${apiUrl}/auth/logout`);
notify('success', data.message);
navigate('/login');
notify("success", data.message);
navigate("/login");
};
const goToMainMenu = async () => {
if (browserId) {
await stopRecording(browserId);
notify('warning', 'Current Recording was terminated');
notify("warning", "Current Recording was terminated");
setBrowserId(null);
}
navigate('/');
navigate("/");
};
const changeLanguage = (lang: string) => {
i18n.changeLanguage(lang); // Change language dynamically
localStorage.setItem("language", lang); // Persist language to localStorage
};
return (
<NavBarWrapper>
<div style={{
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' }} />
<div
style={{
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" }}
/>
</div>
{
user ? (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }}>
{!isRecording ? (
<>
<IconButton
component="a"
href="https://discord.gg/5GbPjBUkws"
target="_blank"
rel="noopener noreferrer"
sx={{
display: 'flex',
alignItems: 'center',
borderRadius: '5px',
padding: '8px',
marginRight: '30px',
{user ? (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
}}
>
{!isRecording ? (
<>
<IconButton
component="a"
href="https://discord.gg/5GbPjBUkws"
target="_blank"
rel="noopener noreferrer"
sx={{
display: "flex",
alignItems: "center",
borderRadius: "5px",
padding: "8px",
marginRight: "30px",
}}
>
<DiscordIcon sx={{ marginRight: "5px" }} />
</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>
<IconButton
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>
</IconButton>
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleMenuClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "right",
}}
transformOrigin={{
vertical: "top",
horizontal: "right",
}}
>
<MenuItem
onClick={() => {
handleMenuClose();
logout();
}}
>
<DiscordIcon sx={{ marginRight: '5px' }} />
</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>
<IconButton 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>
</IconButton>
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleMenuClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
>
<MenuItem onClick={() => { handleMenuClose(); logout(); }}>
<Logout sx={{ marginRight: '5px' }} /> Logout
</MenuItem>
</Menu>
</>
) : (
<>
<IconButton onClick={goToMainMenu} sx={{
borderRadius: '5px',
padding: '8px',
background: 'red',
color: 'white',
marginRight: '10px',
'&:hover': { color: 'white', backgroundColor: 'red' }
}}>
<Clear sx={{ marginRight: '5px' }} />
Discard
</IconButton>
<SaveRecording fileName={recordingName} />
</>
)}
</div>
) : ""
}
<Logout sx={{ marginRight: "5px" }} /> {t("logout")}
</MenuItem>
</Menu>
{/* Language dropdown */}
</>
) : (
<>
<IconButton
onClick={goToMainMenu}
sx={{
borderRadius: "5px",
padding: "8px",
background: "red",
color: "white",
marginRight: "10px",
"&:hover": { color: "white", backgroundColor: "red" },
}}
>
<Clear sx={{ marginRight: "5px" }} />
{t("discard")}
</IconButton>
<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>
) : (
<><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>
);
};
@@ -136,7 +324,7 @@ export const NavBar: React.FC<NavBarProps> = ({ recordingName, isRecording }) =>
const NavBarWrapper = styled.div`
grid-area: navbar;
background-color: white;
padding:5px;
padding: 5px;
display: flex;
justify-content: space-between;
border-bottom: 1px solid #e0e0e0;

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 { BrowserRouter } from 'react-router-dom';
import App from './App';
import i18n from "./i18n"
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement

View File

@@ -5,130 +5,140 @@ import { AuthContext } from "../context/auth";
import { Box, Typography, TextField, Button, CircularProgress, Grid } from "@mui/material";
import { useGlobalInfoStore } from "../context/globalInfo";
import { apiUrl } from "../apiConfig";
import { useTranslation } from 'react-i18next';
import i18n from '../i18n'; // Add this import
const Login = () => {
const [form, setForm] = useState({
email: "",
password: "",
});
const [loading, setLoading] = useState(false);
const { notify } = useGlobalInfoStore();
const { email, password } = form;
const { t } = useTranslation();
console.log(i18n) // Add translation hook
console.log(t)
const [form, setForm] = useState({
email: "",
password: "",
});
const [loading, setLoading] = useState(false);
const { notify } = useGlobalInfoStore();
const { email, password } = form;
const { state, dispatch } = useContext(AuthContext);
const { user } = state;
const { state, dispatch } = useContext(AuthContext);
const { user } = state;
const navigate = useNavigate();
const navigate = useNavigate();
useEffect(() => {
if (user) {
navigate("/");
}
}, [user, navigate]);
useEffect(() => {
if (user) {
navigate("/");
}
}, [user, navigate]);
const handleChange = (e: any) => {
const { name, value } = e.target;
setForm({ ...form, [name]: value });
};
const handleChange = (e: any) => {
const { name, value } = e.target;
setForm({ ...form, [name]: value });
};
const submitForm = async (e: any) => {
e.preventDefault();
setLoading(true);
try {
const { data } = await axios.post(`${apiUrl}/auth/login`, {
email,
password,
});
dispatch({ type: "LOGIN", payload: data });
notify("success", "Welcome to Maxun!");
window.localStorage.setItem("user", JSON.stringify(data));
navigate("/");
} catch (err) {
notify("error", "Login Failed. Please try again.");
setLoading(false);
}
};
const submitForm = async (e: any) => {
e.preventDefault();
setLoading(true);
try {
const { data } = await axios.post(`${apiUrl}/auth/login`, {
email,
password,
});
dispatch({ type: "LOGIN", payload: data });
notify("success", t('login.welcome_notification')); // Translated notification
window.localStorage.setItem("user", JSON.stringify(data));
navigate("/");
} catch (err) {
notify("error", t('login.error_notification')); // Translated error
setLoading(false);
}
};
return (
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
maxHeight: "100vh",
mt: 6,
padding: 4,
}}
>
// Language switcher function
const changeLanguage = (lng: string) => {
i18n.changeLanguage(lng);
};
<Box
component="form"
onSubmit={submitForm}
sx={{
textAlign: "center",
backgroundColor: "#ffffff",
padding: 6,
borderRadius: 5,
boxShadow: "0px 20px 40px rgba(0, 0, 0, 0.2), 0px -5px 10px rgba(0, 0, 0, 0.15)",
display: "flex",
flexDirection: "column",
alignItems: "center",
maxWidth: 400,
width: "100%",
}}
>
<img src="../src/assets/maxunlogo.png" alt="logo" height={55} width={60} style={{ marginBottom: 20, borderRadius: "20%", alignItems: "center" }} />
<Typography variant="h4" gutterBottom>
Welcome Back!
</Typography>
<TextField
fullWidth
label="Email"
name="email"
value={email}
onChange={handleChange}
margin="normal"
variant="outlined"
required
/>
<TextField
fullWidth
label="Password"
name="password"
type="password"
value={password}
onChange={handleChange}
margin="normal"
variant="outlined"
required
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
sx={{ mt: 2, mb: 2 }}
disabled={loading || !email || !password}
>
{loading ? (
<>
<CircularProgress size={20} sx={{ mr: 2 }} />
Loading
</>
) : (
"Login"
)}
</Button>
<Typography variant="body2" align="center">
Dont have an account?{" "}
<Link to="/register" style={{ textDecoration: "none", color: "#ff33cc" }}>
Register
</Link>
</Typography>
</Box>
</Box>
return (
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
maxHeight: "100vh",
mt: 6,
padding: 4,
}}
>
{/* Language Switcher Buttons */}
);
<Box
component="form"
onSubmit={submitForm}
sx={{
textAlign: "center",
backgroundColor: "#ffffff",
padding: 6,
borderRadius: 5,
boxShadow: "0px 20px 40px rgba(0, 0, 0, 0.2), 0px -5px 10px rgba(0, 0, 0, 0.15)",
display: "flex",
flexDirection: "column",
alignItems: "center",
maxWidth: 400,
width: "100%",
}}
>
<img src="../src/assets/maxunlogo.png" alt="logo" height={55} width={60} style={{ marginBottom: 20, borderRadius: "20%", alignItems: "center" }} />
<Typography variant="h4" gutterBottom>
{t('login.title')}
</Typography>
<TextField
fullWidth
label={t('login.email')}
name="email"
value={email}
onChange={handleChange}
margin="normal"
variant="outlined"
required
/>
<TextField
fullWidth
label={t('login.password')}
name="password"
type="password"
value={password}
onChange={handleChange}
margin="normal"
variant="outlined"
required
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
sx={{ mt: 2, mb: 2 }}
disabled={loading || !email || !password}
>
{loading ? (
<>
<CircularProgress size={20} sx={{ mr: 2 }} />
{t('login.loading')}
</>
) : (
t('login.button')
)}
</Button>
<Typography variant="body2" align="center">
{t('login.register_prompt')}{" "}
<Link to="/register" style={{ textDecoration: "none", color: "#ff33cc" }}>
{t('login.register_link')}
</Link>
</Typography>
</Box>
</Box>
);
};
export default Login;