diff --git a/client/src/components/AccountSettings/CustomizeProfile.tsx b/client/src/components/AccountSettings/CustomizeProfile.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a9f84547b8dbd3988c2593221136cc50aba30a38 --- /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 0000000000000000000000000000000000000000..e91015e0764ef5144599e9f9d453ffe0e696957b --- /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 0000000000000000000000000000000000000000..cc941df7b067ac78f18c2b02f21fc591defdaf1d --- /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 0000000000000000000000000000000000000000..813572afbc244d2c36ab751007c7949e3e564f02 --- /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 c0603eef6017c91302a2ca9a900dce73b505a8c7..07827f339d5cd4c090cb2145470706cf505cfcc8 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 37f9707e2cc35c98f9fb924a3e19c27e94ed2ece..358d878e127ad50fc1c4d0cb07d58a633d47cd6b 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 90cfec448051efa161a6c8718947108d83f08fa2..5748c8b5001a96fb7479f13214513e22219dff6e 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 1803933af6ecbe5a703cab6fe56a4f9b8e7b99f6..a590daab85fdf9eab5633455fa74fd067c747dc5 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>