langs
This commit is contained in:
@@ -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
0
public/locales/ar.json
Normal file
13
public/locales/en.json
Normal file
13
public/locales/en.json
Normal 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
13
public/locales/es.json
Normal 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
13
public/locales/ja.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"app": {
|
||||
"name": "Maxun",
|
||||
"version": "beta"
|
||||
},
|
||||
"login": {
|
||||
"title": "おかえりなさい!",
|
||||
"email": "メールアドレス",
|
||||
"password": "パスワード",
|
||||
"button": "ログイン",
|
||||
"register_prompt": "アカウントをお持ちでない方は、新規登録"
|
||||
}
|
||||
}
|
||||
13
public/locales/zh.json
Normal file
13
public/locales/zh.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"app": {
|
||||
"name": "Maxun",
|
||||
"version": "beta"
|
||||
},
|
||||
"login": {
|
||||
"title": "欢迎回来!",
|
||||
"email": "电子邮件",
|
||||
"password": "密码",
|
||||
"button": "登录",
|
||||
"register_prompt": "没有账号?注册"
|
||||
}
|
||||
}
|
||||
29
src/App.tsx
29
src/App.tsx
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
22
src/i18n.ts
Normal 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;
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
Don’t 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;
|
||||
Reference in New Issue
Block a user