From cffda38e44b47935db63c1c73758db3acc39e08d Mon Sep 17 00:00:00 2001 From: Ziwei Wang <ziwei.wang@savoirfairelinux.com> Date: Thu, 16 Mar 2023 15:24:52 -0400 Subject: [PATCH] Add user settings side bar This is only the skeleton and the UI will be implemented when wireframes for the web version are ready The original components and their routes are preserved for now. Change-Id: Ib0900fdd78d6ab5058b7b0278ce92d4741f8be5a --- .../AccountSettings/CustomizeProfile.tsx | 28 ++++ .../AccountSettings/LinkedDevices.tsx | 63 ++++++++ .../AccountSettings/ManageAccount.tsx | 53 +++++++ client/src/components/SettingSidebar.tsx | 137 ++++++++++++++++++ client/src/locale/en/translation.json | 13 +- client/src/locale/fr/translation.json | 13 +- client/src/pages/GeneralSettings.tsx | 11 +- client/src/router.tsx | 15 +- 8 files changed, 320 insertions(+), 13 deletions(-) create mode 100644 client/src/components/AccountSettings/CustomizeProfile.tsx create mode 100644 client/src/components/AccountSettings/LinkedDevices.tsx create mode 100644 client/src/components/AccountSettings/ManageAccount.tsx create mode 100644 client/src/components/SettingSidebar.tsx diff --git a/client/src/components/AccountSettings/CustomizeProfile.tsx b/client/src/components/AccountSettings/CustomizeProfile.tsx new file mode 100644 index 00000000..a9f84547 --- /dev/null +++ b/client/src/components/AccountSettings/CustomizeProfile.tsx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2022 Savoir-faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this program. If not, see + * <https://www.gnu.org/licenses/>. + */ + +import { AvatarEditor } from '../ConversationAvatar'; +function CustomizeProfile() { + return ( + <> + <AvatarEditor /> + </> + ); +} + +export default CustomizeProfile; diff --git a/client/src/components/AccountSettings/LinkedDevices.tsx b/client/src/components/AccountSettings/LinkedDevices.tsx new file mode 100644 index 00000000..e91015e0 --- /dev/null +++ b/client/src/components/AccountSettings/LinkedDevices.tsx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2022 Savoir-faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this program. If not, see + * <https://www.gnu.org/licenses/>. + */ + +import GroupRounded from '@mui/icons-material/AddCircle'; +import Card from '@mui/material/Card'; +import CardContent from '@mui/material/CardContent'; +import Grid from '@mui/material/Grid'; +import ListItem from '@mui/material/ListItem'; +import ListItemText from '@mui/material/ListItemText'; +import Typography from '@mui/material/Typography'; +import { useTranslation } from 'react-i18next'; + +import { useAuthContext } from '../../contexts/AuthProvider'; + +function LinkedDevices() { + const { account } = useAuthContext(); + const { t } = useTranslation(); + const devices: string[][] = []; + const accountDevices = account.devices; + for (const i in accountDevices) devices.push([i, accountDevices[i]]); + //TODO: reduce number of renders + console.log(devices); + + return ( + <> + <Grid item xs={12} sm={6}> + <Card> + <CardContent> + <Typography color="textSecondary" gutterBottom> + {t('settings_linked_devices')} + </Typography> + <Typography gutterBottom variant="h5" component="h2"> + {devices.map((device, i) => ( + <ListItem key={i}> + <GroupRounded /> + <ListItemText id="switch-list-label-rendezvous" primary={device[1]} secondary={device[0]} /> + </ListItem> + ))} + {/* <ListItemTextsion> */} + </Typography> + </CardContent> + </Card> + </Grid> + </> + ); +} + +export default LinkedDevices; diff --git a/client/src/components/AccountSettings/ManageAccount.tsx b/client/src/components/AccountSettings/ManageAccount.tsx new file mode 100644 index 00000000..cc941df7 --- /dev/null +++ b/client/src/components/AccountSettings/ManageAccount.tsx @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 Savoir-faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this program. If not, see + * <https://www.gnu.org/licenses/>. + */ + +import Grid from '@mui/material/Grid'; +import Typography from '@mui/material/Typography'; + +import { useAuthContext } from '../../contexts/AuthProvider'; +import JamiIdCard from '../JamiIdCard'; + +function ManageAccount() { + const { account } = useAuthContext(); + + const devices: string[][] = []; + const accountDevices = account.devices; + for (const i in accountDevices) devices.push([i, accountDevices[i]]); + + console.log(devices); + + const isJamiAccount = account.getType() === 'RING'; + const alias = isJamiAccount ? 'Jami account' : 'SIP account'; + + return ( + <> + <Typography variant="h2" component="h2" gutterBottom> + {alias} + </Typography> + <Grid container spacing={3} style={{ marginBottom: 16 }}> + {isJamiAccount && ( + <Grid item xs={12}> + <JamiIdCard account={account} /> + </Grid> + )} + </Grid> + </> + ); +} + +export default ManageAccount; diff --git a/client/src/components/SettingSidebar.tsx b/client/src/components/SettingSidebar.tsx new file mode 100644 index 00000000..813572af --- /dev/null +++ b/client/src/components/SettingSidebar.tsx @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2022 Savoir-faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this program. If not, see + * <https://www.gnu.org/licenses/>. + */ +import Collapse from '@mui/material/Collapse'; +import List from '@mui/material/List'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemText from '@mui/material/ListItemText'; +// import ListItemIcon from '@mui/material/ListItemIcon'; +import Stack from '@mui/material/Stack'; +import { Theme, useTheme } from '@mui/system'; +import Box from '@mui/system/Box'; +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Outlet, useLocation, useNavigate } from 'react-router-dom'; + +//Settings meta structure +interface ISettingItem { + icon: unknown; + title: string; + highlightKey: string; //The string used to determine whether to highlight the title or not. + link: string; + submenuItems?: ISettingItem[]; +} + +function SettingSidebar() { + const { t } = useTranslation(); + const navigate = useNavigate(); + const theme: Theme = useTheme(); + const { pathname } = useLocation(); + + const isCurrentlyViewing = (text: string) => { + return pathname.includes(text); + }; + + const settingMeta: ISettingItem[] = useMemo(() => { + return [ + { + icon: null, + title: t('settings_title_account'), + highlightKey: 'account', + link: '/settings/account/manage-account', + submenuItems: [ + { + highlightKey: 'manage-account', + icon: null, + title: t('settings_manage_account'), + link: '/settings/account/manage-account', + }, + { + highlightKey: 'customize-profile', + icon: null, + title: t('settings_customize_profile'), + link: '/settings/account/customize-profile', + }, + { + highlightKey: 'linked-devices', + icon: null, + title: t('settings_linked_devices'), + link: '/settings/account/linked-devices', + }, + ], + }, + { + icon: null, + title: t('settings_title_general'), + highlightKey: 'general', + link: '/settings/general/appearance', + submenuItems: [ + { + highlightKey: 'appearance', + icon: null, + title: t('settings_appearance'), + link: '/settings/general/appearance', + }, + ], + }, + ]; + }, [t]); + + return ( + <Stack direction="row"> + <List sx={{ width: '300px' }}> + {settingMeta.map((settingItem, index) => ( + <Box key={index}> + <ListItemButton + selected={isCurrentlyViewing(settingItem.highlightKey)} + onClick={() => navigate(settingItem.link)} + > + {/* TODO: add icons */} + {/* <ListItemIcon> + {/* <InboxIcon /> */} + {/* </ListItemIcon> */} + <ListItemText + primaryTypographyProps={{ + color: isCurrentlyViewing(settingItem.highlightKey) ? theme.palette.primary.dark : 'initial', + }} + primary={settingItem.title} + /> + </ListItemButton> + <Collapse in={isCurrentlyViewing(settingItem.highlightKey)} timeout="auto" unmountOnExit> + <List component="div" disablePadding> + {settingItem.submenuItems && + settingItem.submenuItems.map((submenuItem, itemIndex) => ( + <ListItemButton key={itemIndex} sx={{ pl: 4 }} onClick={() => navigate(submenuItem.link)}> + <ListItemText + primaryTypographyProps={{ + color: isCurrentlyViewing(submenuItem.highlightKey) ? theme.palette.primary.dark : 'initial', + }} + primary={submenuItem.title} + /> + </ListItemButton> + ))} + </List> + </Collapse> + </Box> + ))} + </List> + <Outlet /> + </Stack> + ); +} + +export default SettingSidebar; diff --git a/client/src/locale/en/translation.json b/client/src/locale/en/translation.json index c0603eef..07827f33 100644 --- a/client/src/locale/en/translation.json +++ b/client/src/locale/en/translation.json @@ -2,12 +2,12 @@ "accept_call_audio": "Accept in audio", "accept_call_video": "Accept in video", "admin_page_create_button": "Create admin account", + "admin_page_login_title": "Jami Web Admin Login", "admin_page_password_placeholder": "New password", "admin_page_password_repeat_placeholder": "Repeat password", "admin_page_setup_complete": "Setup is already complete", "admin_page_setup_intro": "Let's start by creating a new administrator account to control access to the server configuration.", "admin_page_setup_not_complete": "Setup is not complete", - "admin_page_login_title": "Jami Web Admin Login", "admin_page_setup_title": "Jami Web Admin Setup", "admin_page_welcome": "Welcome to the Jami web node setup.", "are_composing_1": "{{member0}} is writing", @@ -132,12 +132,17 @@ "registration_success": "You've successfully registered! — Logging you in...", "search_results": "Search Results", "select_placeholder": "Select an option", - "setting_dark_theme": "Dark theme", - "setting_language": "Language", - "setting_link_preview": "Show link previews", + "settings_appearance": "Appearance", + "settings_customize_profile": "Customize profile", + "settings_dark_theme": "Dark theme", + "settings_language": "Language", + "settings_link_preview": "Show link previews", + "settings_linked_devices": "Linked devices", + "settings_manage_account": "Manage account", "settings_menu_item_account": "Account settings", "settings_menu_item_contacts": "Contacts", "settings_menu_item_general": "General settings", + "settings_title_account": "Account", "settings_title_chat": "Chat", "settings_title_general": "General", "settings_title_system": "System", diff --git a/client/src/locale/fr/translation.json b/client/src/locale/fr/translation.json index 37f9707e..358d878e 100644 --- a/client/src/locale/fr/translation.json +++ b/client/src/locale/fr/translation.json @@ -2,12 +2,12 @@ "accept_call_audio": "Accepter en audio", "accept_call_video": "Accepter en vidéo", "admin_page_create_button": "Créer un compte admin", + "admin_page_login_title": "Jami web — administration — connexion", "admin_page_password_placeholder": "Nouveau mot de passe", "admin_page_password_repeat_placeholder": "Répéter le mot de passe", "admin_page_setup_complete": "La configuration est déjà terminée", "admin_page_setup_intro": "Commençons par créer un nouveau compte administrateur pour contrôler l'accès à la configuration du serveur.", "admin_page_setup_not_complete": "La configuration n'est pas terminée", - "admin_page_login_title": "Jami web — administration — connexion", "admin_page_setup_title": "Jami web — administration — configuration", "admin_page_welcome": "Bienvenue à la configuration du nœud web Jami.", "are_composing_1": "{{member0}} est en train d'écrire", @@ -132,12 +132,17 @@ "registration_success": "Inscription réussie! — Connexion en cours...", "search_results": "Résultats de la recherche", "select_placeholder": "Sélectionner une option", - "setting_dark_theme": "Thème sombre", - "setting_language": "Langue", - "setting_link_preview": "Montrer la prévisualisation des liens", + "settings_appearance": "Apparence", + "settings_customize_profile": "Paramétrer le profil", + "settings_dark_theme": "Thème sombre", + "settings_language": "Langue", + "settings_link_preview": "Montrer la prévisualisation des liens", + "settings_linked_devices": "Appareils associés", + "settings_manage_account": "Gérer le compte", "settings_menu_item_account": "Paramètres du compte", "settings_menu_item_contacts": "Contacts", "settings_menu_item_general": "Paramètres généraux", + "settings_title_account": "Compte", "settings_title_chat": "Clavardage", "settings_title_general": "Général", "settings_title_system": "Système", diff --git a/client/src/pages/GeneralSettings.tsx b/client/src/pages/GeneralSettings.tsx index 90cfec44..5748c8b5 100644 --- a/client/src/pages/GeneralSettings.tsx +++ b/client/src/pages/GeneralSettings.tsx @@ -45,7 +45,7 @@ const SettingTheme = () => { const { mode, toggleMode } = useContext(CustomThemeContext); - return <SettingSwitch label={t('setting_dark_theme')} onChange={toggleMode} checked={mode === 'dark'} />; + return <SettingSwitch label={t('settings_dark_theme')} onChange={toggleMode} checked={mode === 'dark'} />; }; const settingLanguageOptions = languagesInfos.map(({ tag, fullName }) => ({ @@ -72,7 +72,12 @@ const SettingLanguage = () => { ); return ( - <SettingSelect label={t('setting_language')} option={option} options={settingLanguageOptions} onChange={onChange} /> + <SettingSelect + label={t('settings_language')} + option={option} + options={settingLanguageOptions} + onChange={onChange} + /> ); }; @@ -81,5 +86,5 @@ const SettingLinkPreview = () => { const [isOn, setIsOn] = useState<boolean>(true); - return <SettingSwitch label={t('setting_link_preview')} onChange={() => setIsOn((isOn) => !isOn)} checked={isOn} />; + return <SettingSwitch label={t('settings_link_preview')} onChange={() => setIsOn((isOn) => !isOn)} checked={isOn} />; }; diff --git a/client/src/router.tsx b/client/src/router.tsx index 1803933a..a590daab 100644 --- a/client/src/router.tsx +++ b/client/src/router.tsx @@ -18,9 +18,13 @@ import { createBrowserRouter, createRoutesFromElements, Outlet, Route } from 'react-router-dom'; import App, { appLoader } from './App'; +import CustomizeProfile from './components/AccountSettings/CustomizeProfile'; +import LinkedDevices from './components/AccountSettings/LinkedDevices'; +import ManageAccount from './components/AccountSettings/ManageAccount'; import ContactList from './components/ContactList'; import ConversationView from './components/ConversationView'; import Header from './components/Header'; +import SettingSidebar from './components/SettingSidebar'; import AuthProvider from './contexts/AuthProvider'; import CallManagerProvider from './contexts/CallManagerProvider'; import ConversationProvider from './contexts/ConversationProvider'; @@ -80,8 +84,15 @@ export const router = createBrowserRouter( } /> </Route> - <Route path="settings-account" element={<AccountSettings />} /> - <Route path="settings-general" element={<GeneralSettings />} /> + <Route element={<SettingSidebar />}> + <Route path="settings/account/manage-account" element={<ManageAccount />} /> + <Route path="settings/account/customize-profile" element={<CustomizeProfile />} /> + <Route path="settings/account/linked-devices" element={<LinkedDevices />} /> + <Route path="settings/general/appearance" element={<GeneralSettings />} /> + + <Route path="settings-account" element={<AccountSettings />} /> + <Route path="settings-general" element={<GeneralSettings />} /> + </Route> <Route path="contacts" element={<ContactList />} /> </Route> </Route> -- GitLab