-
Notifications
You must be signed in to change notification settings - Fork 13.7k
refactor: Revamp admin settings — card-based layout with section navigation #41339
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@rocket.chat/meteor': minor | ||
| --- | ||
|
|
||
| Refreshes the look of every admin settings group. The shared settings template now renders each section as a card with an in-page section navigation ("Settings in …") on the right, instead of a single accordion. All existing setting groups, sections, names, and descriptions are preserved — this is a presentational refactor of the settings rendering components, so it applies uniformly across the admin without reorganizing any settings. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,70 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Box } from '@rocket.chat/fuselage'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { MouseEvent } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useEffect, useState } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useTranslation } from 'react-i18next'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export type SettingsSectionNavItem = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| label: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type SettingsSectionNavProps = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| groupLabel: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| items: SettingsSectionNavItem[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const SettingsSectionNav = ({ groupLabel, items }: SettingsSectionNavProps) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { t } = useTranslation(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [activeId, setActiveId] = useState(items[0]?.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setActiveId((current) => (items.some((item) => item.id === current) ? current : items[0]?.id)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [items]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleClick = (event: MouseEvent<HTMLAnchorElement>, id: string): void => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| event.preventDefault(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setActiveId(id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| document.getElementById(id)?.scrollIntoView({ behavior: 'smooth', block: 'start' }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Box | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| is='nav' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| width='x240' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| flexShrink={0} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pi={24} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pbs={24} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| display='flex' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| flexDirection='column' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| overflowY='auto' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| borderInlineStartWidth='default' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| borderInlineStartColor='light' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Box is='h3' mbe={16} color='hint' fontScale='micro' textTransform='uppercase'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {t('Settings_in_group', { group: groupLabel })} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Box> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {items.map(({ id, label }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isActive = id === activeId; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Box | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key={id} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| is='a' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| href={`#${id}`} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={(event: MouseEvent<HTMLAnchorElement>) => handleClick(event, id)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pb={8} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pis={12} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mbe={4} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fontScale='p2' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| color={isActive ? 'info' : 'hint'} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fontWeight={isActive ? 700 : 400} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: The current section is conveyed only by color/weight. Expose the active navigation item with Prompt for AI agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| style={{ cursor: 'pointer', textDecoration: 'none' }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {label} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Box> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+46
to
+64
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win Add The active link is visually distinguished by ♿ Proposed fix fontScale='p2'
color={isActive ? 'info' : 'hint'}
fontWeight={isActive ? 700 : 400}
+ aria-current={isActive ? 'true' : undefined}
style={{ cursor: 'pointer', textDecoration: 'none' }}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Box> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default SettingsSectionNav; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,25 +4,31 @@ import { capitalize } from '@rocket.chat/string-helpers'; | |
| import { GenericModal } from '@rocket.chat/ui-client'; | ||
| import { useToastMessageDispatch, useAbsoluteUrl, useMethod, useTranslation, useSetModal } from '@rocket.chat/ui-contexts'; | ||
| import DOMPurify from 'dompurify'; | ||
| import { memo, useEffect, useState } from 'react'; | ||
| import { memo, useEffect, useMemo, useState } from 'react'; | ||
|
|
||
| import CreateOAuthModal from './CreateOAuthModal'; | ||
| import { strRight } from '../../../../../../lib/utils/stringUtils'; | ||
| import { useEditableSettingsGroupSections } from '../../../EditableSettingsContext'; | ||
| import SettingsGroupPage from '../../SettingsGroupPage'; | ||
| import SettingsSection from '../../SettingsSection'; | ||
| import { getSettingsSectionAnchorId } from '../getSettingsSectionAnchorId'; | ||
|
|
||
| export type OAuthGroupPageProps = ISetting & { | ||
| onClickBack?: () => void; | ||
| }; | ||
|
|
||
| function OAuthGroupPage({ _id, onClickBack, ...group }: OAuthGroupPageProps) { | ||
| const sections = useEditableSettingsGroupSections(_id); | ||
| const solo = sections.length === 1; | ||
| const t = useTranslation(); | ||
|
|
||
| const [settingSections, setSettingSections] = useState(sections); | ||
|
|
||
| const navItems = useMemo( | ||
| () => | ||
| settingSections.filter(Boolean).map((sectionName) => ({ id: getSettingsSectionAnchorId(_id, sectionName), label: t(sectionName) })), | ||
| [settingSections, _id, t], | ||
| ); | ||
|
Comment on lines
+26
to
+30
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Inspect the useTranslation import/type used in OAuthGroupPage.tsx and its t() signature.
sed -n '1,10p' apps/meteor/client/views/admin/settings/groups/OAuthGroupPage/OAuthGroupPage.tsx
rg -n "export.*useTranslation" packages/ui-contexts/src 2>/dev/nullRepository: RocketChat/Rocket.Chat Length of output: 980 🏁 Script executed: #!/bin/bash
sed -n '1,220p' packages/ui-contexts/src/hooks/useTranslation.ts
printf '\n---\n'
rg -n "type TranslationContextValue|interface TranslationContextValue|TranslationKey" packages/ui-contexts/src packages/ui-contexts -g '!**/dist/**'Repository: RocketChat/Rocket.Chat Length of output: 2467 🏁 Script executed: #!/bin/bash
sed -n '1,220p' apps/meteor/client/views/admin/settings/groups/OAuthGroupPage/OAuthGroupPage.tsx
printf '\n---\n'
rg -n "useEditableSettingsGroupSections|settingSections" apps/meteor/client/views/admin/settings -g '!**/dist/**'Repository: RocketChat/Rocket.Chat Length of output: 6871 🏁 Script executed: #!/bin/bash
sed -n '1,220p' apps/meteor/client/views/admin/settings/EditableSettingsContext.tsx
printf '\n---\n'
rg -n "useEditableSettingsGroupSections" apps/meteor/client/views/admin/settings -g '!**/dist/**'Repository: RocketChat/Rocket.Chat Length of output: 1180 🏁 Script executed: #!/bin/bash
fd -a "EditableSettingsContext.*" apps/meteor/client/views/admin/settings
printf '\n---\n'
fd -a "EditableSettingsContext.*" apps/meteor/client/views/admin
printf '\n---\n'
git ls-files | rg "EditableSettingsContext\.(ts|tsx)$"Repository: RocketChat/Rocket.Chat Length of output: 388 🏁 Script executed: #!/bin/bash
sed -n '1,240p' apps/meteor/client/views/admin/EditableSettingsContext.ts
printf '\n---\n'
sed -n '1,220p' apps/meteor/client/views/admin/settings/groups/GenericGroupPage.tsx
printf '\n---\n'
sed -n '1,220p' apps/meteor/client/views/admin/settings/groups/SettingsSection.tsxRepository: RocketChat/Rocket.Chat Length of output: 5995 Add the 🤖 Prompt for AI Agents |
||
|
|
||
| const sectionIsCustomOAuth = (sectionName: string): string | boolean => sectionName && /^Custom OAuth:\s.+/.test(sectionName); | ||
|
|
||
| const getAbsoluteUrl = useAbsoluteUrl(); | ||
|
|
@@ -98,6 +104,7 @@ function OAuthGroupPage({ _id, onClickBack, ...group }: OAuthGroupPageProps) { | |
| _id={_id} | ||
| {...group} | ||
| onClickBack={onClickBack} | ||
| navItems={navItems} | ||
| headerButtons={ | ||
| <> | ||
| <Button onClick={handleRefreshOAuthServicesButtonClick}>{t('Refresh_oauth_services')}</Button> | ||
|
|
@@ -114,6 +121,7 @@ function OAuthGroupPage({ _id, onClickBack, ...group }: OAuthGroupPageProps) { | |
| return ( | ||
| <SettingsSection | ||
| key={sectionName} | ||
| id={getSettingsSectionAnchorId(_id, sectionName)} | ||
| groupId={_id} | ||
| help={ | ||
| <span | ||
|
|
@@ -123,7 +131,6 @@ function OAuthGroupPage({ _id, onClickBack, ...group }: OAuthGroupPageProps) { | |
| /> | ||
| } | ||
| sectionName={sectionName} | ||
| solo={solo} | ||
| > | ||
| <div className='submit'> | ||
| <Button secondary danger onClick={handleRemoveCustomOAuthButtonClick}> | ||
|
|
@@ -134,7 +141,9 @@ function OAuthGroupPage({ _id, onClickBack, ...group }: OAuthGroupPageProps) { | |
| ); | ||
| } | ||
|
|
||
| return <SettingsSection key={sectionName} groupId={_id} sectionName={sectionName} solo={solo} />; | ||
| return ( | ||
| <SettingsSection key={sectionName} id={getSettingsSectionAnchorId(_id, sectionName)} groupId={_id} sectionName={sectionName} /> | ||
| ); | ||
| })} | ||
| </SettingsGroupPage> | ||
| ); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P3: Section navigation does not update the URL fragment, so a selected Accounts section cannot be bookmarked/shared and browser history has no section state. Preserve/update the hash while retaining smooth scrolling.
Prompt for AI agents