diff --git a/meshapp/src/components/svgs/discord.tsx b/meshapp/src/components/svgs/discord.tsx new file mode 100644 index 00000000..fff63085 --- /dev/null +++ b/meshapp/src/components/svgs/discord.tsx @@ -0,0 +1,28 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; +import React from "react"; + +const DiscordSvg: React.FC = (props: SvgIconProps) => { + return ( + + + + + + discord + + + + + ); +}; + +export default DiscordSvg; diff --git a/meshapp/src/components/svgs/google.tsx b/meshapp/src/components/svgs/google.tsx new file mode 100644 index 00000000..f2aac772 --- /dev/null +++ b/meshapp/src/components/svgs/google.tsx @@ -0,0 +1,61 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; +import React from "react"; + +const GoogleSvg: React.FC = (props: SvgIconProps) => { + return ( + + + + + + Google-color Created with Sketch. + + + + + + + + + + + + + + + ); +}; + +export default GoogleSvg; diff --git a/meshapp/src/components/svgs/logo.tsx b/meshapp/src/components/svgs/logo.tsx new file mode 100644 index 00000000..55741d82 --- /dev/null +++ b/meshapp/src/components/svgs/logo.tsx @@ -0,0 +1,111 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; +import React from "react"; + +const MeshLogo: React.FC = (props: SvgIconProps) => { + return ( + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default MeshLogo; diff --git a/meshapp/src/components/svgs/mesh-pfp.tsx b/meshapp/src/components/svgs/mesh-pfp.tsx new file mode 100644 index 00000000..b36771c0 --- /dev/null +++ b/meshapp/src/components/svgs/mesh-pfp.tsx @@ -0,0 +1,38 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; +import React from "react"; + +const MeshPfp: React.FC = (props: SvgIconProps) => { + return ( + + + {/* */} + + + + + + + + + + ); +}; + +export default MeshPfp; diff --git a/meshapp/src/pages/Settings/Page.tsx b/meshapp/src/pages/Settings/Page.tsx new file mode 100644 index 00000000..1d3da73d --- /dev/null +++ b/meshapp/src/pages/Settings/Page.tsx @@ -0,0 +1,170 @@ +import React from "react"; + +import { Box, Divider, Stack, Tab, Tabs, styled } from "@mui/material"; +import MyAccountSetting from "./components/TabMyAccount"; +import AppearanceSetting from "./components/TabAppearance"; +import { GridContainer } from "src/components/ui/Grid"; + +interface StyledTabProps { + label: string; +} + +const CustomTab = styled((props: StyledTabProps) => ( + +))(({ theme }) => ({ + textTransform: "none", + alignContent: "left", + minWidth: 0, + [theme.breakpoints.up("sm")]: { + minWidth: 0, + }, + height: "fit-content", + fontWeight: theme.typography.fontWeightRegular, + marginLeft: theme.spacing(1), + marginRight: theme.spacing(1), + padding: "8px", + borderRadius: "8px", + color: "rgba(0, 0, 0, 0.85)", + "&:hover": { + color: theme.palette.primary.main, + opacity: 1, + }, + "&.Mui-selected": { + fontWeight: theme.typography.fontWeightMedium, + backgroundColor: theme.palette.primary.dark, + color: "white", + }, + "&.Mui-focusVisible": { + backgroundColor: "#d1eaff", + }, +})); + +interface TabPanelProps { + children?: React.ReactNode; + index: number; + value: number; +} + +function TabPanel(props: TabPanelProps) { + const { children, value, index, ...other } = props; + + return ( + + ); +} + +function a11yProps(index: number) { + return { + id: `vertical-tab-${index}`, + "aria-controls": `vertical-tabpanel-${index}`, + }; +} + +const SettingPage = () => { + const [value, setValue] = React.useState(0); + + const handleChange = (event: React.SyntheticEvent, newValue: number) => { + setValue(newValue); + }; + + return ( + + + + + + + + + + + + + + + + + + + + Blocked Accounts + + + + + + Notifications + + + Language + + + What's New + + + Contact Us + + + + + ); +}; + +export default SettingPage; diff --git a/meshapp/src/pages/Settings/components/ChangePasswordButton.tsx b/meshapp/src/pages/Settings/components/ChangePasswordButton.tsx new file mode 100644 index 00000000..32156a0c --- /dev/null +++ b/meshapp/src/pages/Settings/components/ChangePasswordButton.tsx @@ -0,0 +1,127 @@ +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + TextField, + useTheme, +} from "@mui/material"; +import { FormEvent, useState } from "react"; +import { useAccountContext } from "../../../contexts/UserContext"; +import { toast } from "react-toastify"; +import { useChangePassword } from "../hooks/useChangePassword"; + +const ChangePasswordButton = () => { + const theme = useTheme(); + const { account } = useAccountContext(); + const [open, setOpen] = useState(false); + + const handleClickOpen = () => { + setOpen(true); + }; + + const handleClose = () => { + setOpen(false); + }; + + const { loadingState, changePassword } = useChangePassword(); + + const handleSubmit = async (event: FormEvent) => { + if (!account) return; + event.preventDefault(); + const formData = new FormData(event.currentTarget); + const oldPassword = formData.get("oldPassword"); + const newPassword = formData.get("newPassword"); + const confirmPassword = formData.get("confirmPassword"); + + if (!oldPassword || !newPassword) return; + if (newPassword !== confirmPassword) { + toast.warning("New passwords do not match."); + return; + } + + const success = await changePassword( + oldPassword.toString(), + newPassword.toString() + ); + + if (success) { + handleClose(); + } + }; + + return ( + <> + + +
+ Change Password + + + To change your password, please enter the required fields. + + + + + + + + + +
+
+ + ); +}; + +export default ChangePasswordButton; diff --git a/meshapp/src/pages/Settings/components/CustomThemeSwitch.tsx b/meshapp/src/pages/Settings/components/CustomThemeSwitch.tsx new file mode 100644 index 00000000..e205559f --- /dev/null +++ b/meshapp/src/pages/Settings/components/CustomThemeSwitch.tsx @@ -0,0 +1,49 @@ +import { Switch, styled } from "@mui/material"; + +const CustomThemeSwitch = styled(Switch)(({ theme }) => ({ + width: 62, + height: 34, + padding: 7, + "& .MuiSwitch-switchBase": { + margin: 1, + padding: 0, + transform: "translateX(6px)", + "&.Mui-checked": { + color: "#fff", + transform: "translateX(22px)", + "& .MuiSwitch-thumb:before": { + backgroundImage: `url('data:image/svg+xml;utf8,')`, + }, + "& + .MuiSwitch-track": { + opacity: 1, + backgroundColor: theme.palette.mode === "dark" ? "#8796A5" : "#aab4be", + }, + }, + }, + "& .MuiSwitch-thumb": { + backgroundColor: theme.palette.primary.main, + width: 32, + height: 32, + "&::before": { + content: "''", + position: "absolute", + width: "100%", + height: "100%", + left: 0, + top: 0, + backgroundRepeat: "no-repeat", + backgroundPosition: "center", + backgroundImage: `url('data:image/svg+xml;utf8,')`, + }, + }, + "& .MuiSwitch-track": { + opacity: 1, + backgroundColor: theme.palette.mode === "dark" ? "#8796A5" : "#aab4be", + borderRadius: 20 / 2, + }, +})); +export default CustomThemeSwitch; diff --git a/meshapp/src/pages/Settings/components/TabAppearance.tsx b/meshapp/src/pages/Settings/components/TabAppearance.tsx new file mode 100644 index 00000000..ff828020 --- /dev/null +++ b/meshapp/src/pages/Settings/components/TabAppearance.tsx @@ -0,0 +1,37 @@ +import { Typography, FormControlLabel, useTheme } from "@mui/material"; +import CustomThemeSwitch from "./CustomThemeSwitch"; +import { useAccountContext } from "src/contexts/UserContext"; +import { useThemeContext } from "src/themes/ThemeContextProvider"; + +const AppearanceSetting = () => { + const { account } = useAccountContext(); + const theme = useTheme(); + const colorMode = useThemeContext(); + + if (!account) return null; + return ( + <> +
+ + Theme + + + } + label={ + + {theme.palette.mode} + + } + /> +
+ + ); +}; + +export default AppearanceSetting; diff --git a/meshapp/src/pages/Settings/components/TabMyAccount.tsx b/meshapp/src/pages/Settings/components/TabMyAccount.tsx new file mode 100644 index 00000000..7d0eef39 --- /dev/null +++ b/meshapp/src/pages/Settings/components/TabMyAccount.tsx @@ -0,0 +1,188 @@ +import { Box, Button, Divider, Typography, useTheme } from "@mui/material"; +import { useAccountContext } from "src/contexts/UserContext"; +import { FC } from "react"; +import { + Security as SecurityIcon, + Lock, + LockOpen, + GppMaybe, + Verified, +} from "@mui/icons-material"; +import ChangePasswordButton from "./ChangePasswordButton"; + +const MyAccountSetting = () => { + const { account } = useAccountContext(); + const theme = useTheme(); + + if (!account) return null; + return ( + <> + +
+ + My Account + + {account.settings.isVerified ? ( + + ) : ( + + )} +
+ + +
+ + + + Password and Authentication + + + +
+ {account.settings.is2FAEnabled ? ( + <> + + Multi-factor Authentication Enabled + + ) : ( + <> + + Multi-factor Authentication Disabled + + )} +
+
+ +
+ + +
+ + Authentication +
+
+ +
+
+ + + + Account Removal + + + Disabling your account means you can recover it at anytime after + taking this action + + + + + + + + ); +}; + +export default MyAccountSetting; + +interface AccountInfoProps { + title: String; + value: String; +} +const AccountInfo: FC = ({ title, value }) => { + return ( + +
+ + {title} + + {value} +
+
+ +
+
+ ); +}; diff --git a/meshapp/src/pages/Settings/hooks/useChangePassword.tsx b/meshapp/src/pages/Settings/hooks/useChangePassword.tsx new file mode 100644 index 00000000..61fd3fd8 --- /dev/null +++ b/meshapp/src/pages/Settings/hooks/useChangePassword.tsx @@ -0,0 +1,40 @@ +import { toast } from "react-toastify"; + +import { useState } from "react"; +import { LoadingState } from "src/models/loading"; +import { useAccountContext } from "src/contexts/UserContext"; +import { axiosInstance } from "src/config/axios-config"; + +export const useChangePassword = () => { + const { account } = useAccountContext(); + const [loadingState, setLoadingState] = + useState("initializing"); + + const changePassword = async (oldPassword: String, newPassword: String) => { + if (!account) { + toast.error("User not found."); + return false; + } + + try { + setLoadingState("loading"); + const response = await axiosInstance.patch("/accounts/change-password/", { + email: account.email, + old_password: oldPassword, + new_password: newPassword, + }); + + if (response.status === 204) { + toast.success("Password successfully changed."); + return true; + } + } catch (error) { + toast.error("Invalid old password."); + return false; + } finally { + setLoadingState("completed"); + } + }; + + return { loadingState, changePassword }; +}; diff --git a/meshapp/src/routes/MainPageRoutes.tsx b/meshapp/src/routes/MainPageRoutes.tsx index 1f4586b0..774c75dd 100644 --- a/meshapp/src/routes/MainPageRoutes.tsx +++ b/meshapp/src/routes/MainPageRoutes.tsx @@ -9,8 +9,7 @@ import PasswordReset from "src/components/AuthForms/ForgotPasswordForm"; import ProfilePage from "src/pages/Profiles/Page"; import { exampleProfile } from "src/pages/Profiles/tests/profile-examples"; import Swiper from "src/pages/Swiping/Page"; -// import SettingPage from "../pages/setting"; -// import MessagePage from "../pages/messaging"; +import SettingPage from "../pages/Settings/Page"; //contains routes for the main page export default function MainPageRoutes() { @@ -22,8 +21,7 @@ export default function MainPageRoutes() { } /> } /> } /> - {/* } /> - } /> */} + } /> }