From 8d68c6652a06e159422cd3322a1ffb1197cf5922 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 6 Oct 2020 13:06:58 +0200 Subject: [PATCH] Add settings page and remove popup --- packages/app/src/App.tsx | 2 + packages/app/src/components/Root/Root.tsx | 4 +- .../src/layout/Sidebar/Sidebar.stories.tsx | 2 - plugins/user-settings/package.json | 1 + ...AuthProviderList.tsx => AuthProviders.tsx} | 7 +- .../DefaultProviderSettings.test.tsx | 2 +- ...{FeatureFlagsList.tsx => FeatureFlags.tsx} | 31 ++--- .../{AppSettingsList.tsx => General.tsx} | 25 ++-- .../src/components/PinButton.test.tsx | 2 +- .../user-settings/src/components/Profile.tsx | 54 +++++++++ .../{UserSettings.test.tsx => Settings.tsx} | 26 +++-- .../src/components/SettingsDialog.test.tsx | 56 --------- .../src/components/SettingsDialog.tsx | 110 ------------------ .../src/components/SettingsPage.tsx | 64 ++++++++++ .../src/components/SignInAvatar.tsx | 14 +-- .../src/components/ThemeToggle.test.tsx | 2 +- .../src/components/UserSettings.tsx | 94 --------------- .../src/components/UserSettingsMenu.test.tsx | 2 +- plugins/user-settings/src/index.ts | 3 +- plugins/user-settings/src/plugin.ts | 7 +- 20 files changed, 198 insertions(+), 310 deletions(-) rename plugins/user-settings/src/components/{AuthProviderList.tsx => AuthProviders.tsx} (81%) rename plugins/user-settings/src/components/{FeatureFlagsList.tsx => FeatureFlags.tsx} (78%) rename plugins/user-settings/src/components/{AppSettingsList.tsx => General.tsx} (63%) create mode 100644 plugins/user-settings/src/components/Profile.tsx rename plugins/user-settings/src/components/{UserSettings.test.tsx => Settings.tsx} (55%) delete mode 100644 plugins/user-settings/src/components/SettingsDialog.test.tsx delete mode 100644 plugins/user-settings/src/components/SettingsDialog.tsx create mode 100644 plugins/user-settings/src/components/SettingsPage.tsx delete mode 100644 plugins/user-settings/src/components/UserSettings.tsx diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 667b14366f..81d08f58a9 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -33,6 +33,7 @@ import { Router as GraphiQLRouter } from '@backstage/plugin-graphiql'; import { Router as TechRadarRouter } from '@backstage/plugin-tech-radar'; import { Router as LighthouseRouter } from '@backstage/plugin-lighthouse'; import { Router as RegisterComponentRouter } from '@backstage/plugin-register-component'; +import { Router as SettingsRouter } from '@backstage/plugin-user-settings'; import { Route, Routes, Navigate } from 'react-router'; import { EntityPage } from './components/catalog/EntityPage'; @@ -81,6 +82,7 @@ const AppRoutes = () => ( path="/register-component" element={} /> + } /> {...deprecatedAppRoutes} ); diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index 9f5387cea8..e947e5c91d 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -38,7 +38,7 @@ import { } from '@backstage/core'; import { NavLink } from 'react-router-dom'; import { graphiQLRouteRef } from '@backstage/plugin-graphiql'; -import { UserSettings } from '@backstage/plugin-user-settings'; +import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; const useSidebarLogoStyles = makeStyles({ root: { @@ -102,7 +102,7 @@ const Root: FC<{}> = ({ children }) => ( /> - + {children} diff --git a/packages/core/src/layout/Sidebar/Sidebar.stories.tsx b/packages/core/src/layout/Sidebar/Sidebar.stories.tsx index 14bf6e69ed..ec5746d428 100644 --- a/packages/core/src/layout/Sidebar/Sidebar.stories.tsx +++ b/packages/core/src/layout/Sidebar/Sidebar.stories.tsx @@ -56,7 +56,6 @@ const handleSearch = (input: string) => { export const SampleSidebar = () => ( - {/* */} @@ -92,7 +91,6 @@ const apis = ApiRegistry.from([ export const WithUserSettingsPlugin = () => ( - {/* */} diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 1c5d489747..c744f757c5 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -28,6 +28,7 @@ "@material-ui/lab": "4.0.0-alpha.45", "react": "^16.13.1", "react-dom": "^16.13.1", + "react-router": "6.0.0-beta.0", "react-use": "^15.3.3" }, "devDependencies": { diff --git a/plugins/user-settings/src/components/AuthProviderList.tsx b/plugins/user-settings/src/components/AuthProviders.tsx similarity index 81% rename from plugins/user-settings/src/components/AuthProviderList.tsx rename to plugins/user-settings/src/components/AuthProviders.tsx index 9a2096212d..917e70b067 100644 --- a/plugins/user-settings/src/components/AuthProviderList.tsx +++ b/plugins/user-settings/src/components/AuthProviders.tsx @@ -16,11 +16,14 @@ import React from 'react'; import { List } from '@material-ui/core'; +import { InfoCard } from '@backstage/core'; type Props = { providers: React.ReactNode; }; -export const AuthProvidersList = ({ providers }: Props) => ( - {providers} +export const AuthProviders = ({ providers }: Props) => ( + + {providers} + ); diff --git a/plugins/user-settings/src/components/DefaultProviderSettings.test.tsx b/plugins/user-settings/src/components/DefaultProviderSettings.test.tsx index 8f6e4b72dc..bc4e0da363 100644 --- a/plugins/user-settings/src/components/DefaultProviderSettings.test.tsx +++ b/plugins/user-settings/src/components/DefaultProviderSettings.test.tsx @@ -23,7 +23,7 @@ import { } from '@backstage/core'; import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; import { fireEvent } from '@testing-library/react'; -import * as React from 'react'; +import React from 'react'; import { DefaultProviderSettings } from './DefaultProviderSettings'; const mockSignInHandler = jest.fn().mockReturnValue(''); diff --git a/plugins/user-settings/src/components/FeatureFlagsList.tsx b/plugins/user-settings/src/components/FeatureFlags.tsx similarity index 78% rename from plugins/user-settings/src/components/FeatureFlagsList.tsx rename to plugins/user-settings/src/components/FeatureFlags.tsx index 2636d0e66f..d3f21a7c6e 100644 --- a/plugins/user-settings/src/components/FeatureFlagsList.tsx +++ b/plugins/user-settings/src/components/FeatureFlags.tsx @@ -22,12 +22,13 @@ import { FeatureFlagName, FeatureFlagState, FeatureFlagsRegistryItem, + InfoCard, } from '@backstage/core'; import { FlagItem } from './FeatureFlagsItem'; type Props = { featureFlags: FeatureFlagsRegistryItem[] }; -export const FeatureFlagsList = ({ featureFlags }: Props) => { +export const FeatureFlags = ({ featureFlags }: Props) => { const featureFlagApi = useApi(featureFlagsApiRef); const initialFlagState = featureFlags.reduce( (result, featureFlag: FeatureFlagsRegistryItem) => { @@ -57,19 +58,21 @@ export const FeatureFlagsList = ({ featureFlags }: Props) => { ); return ( - - {featureFlags.map(featureFlag => { - const enabled = Boolean(state[featureFlag.name]); + + + {featureFlags.map(featureFlag => { + const enabled = Boolean(state[featureFlag.name]); - return ( - - ); - })} - + return ( + + ); + })} + + ); }; diff --git a/plugins/user-settings/src/components/AppSettingsList.tsx b/plugins/user-settings/src/components/General.tsx similarity index 63% rename from plugins/user-settings/src/components/AppSettingsList.tsx rename to plugins/user-settings/src/components/General.tsx index 3f2b455520..00ac17dfe1 100644 --- a/plugins/user-settings/src/components/AppSettingsList.tsx +++ b/plugins/user-settings/src/components/General.tsx @@ -13,14 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { InfoCard } from '@backstage/core'; +import { Grid, List } from '@material-ui/core'; import React from 'react'; -import { List } from '@material-ui/core'; -import { ThemeToggle } from './ThemeToggle'; import { PinButton } from './PinButton'; +import { Profile } from './Profile'; +import { ThemeToggle } from './ThemeToggle'; -export const AppSettingsList = () => ( - - - - +export const General = () => ( + + + + + + + + + + + + + ); diff --git a/plugins/user-settings/src/components/PinButton.test.tsx b/plugins/user-settings/src/components/PinButton.test.tsx index a800193c06..76af8ce8d4 100644 --- a/plugins/user-settings/src/components/PinButton.test.tsx +++ b/plugins/user-settings/src/components/PinButton.test.tsx @@ -17,7 +17,7 @@ import { SidebarPinStateContext } from '@backstage/core'; import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; import { fireEvent } from '@testing-library/react'; -import * as React from 'react'; +import React from 'react'; import { PinButton } from './PinButton'; describe('', () => { diff --git a/plugins/user-settings/src/components/Profile.tsx b/plugins/user-settings/src/components/Profile.tsx new file mode 100644 index 0000000000..5bb9485f8d --- /dev/null +++ b/plugins/user-settings/src/components/Profile.tsx @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { InfoCard } from '@backstage/core'; +import { Grid, Typography } from '@material-ui/core'; +import React from 'react'; +import { SignInAvatar } from './SignInAvatar'; +import { UserSettingsMenu } from './UserSettingsMenu'; +import { useUserProfile } from './useUserProfileInfo'; + +export const Profile = () => { + const { profile, displayName } = useUserProfile(); + + return ( + + + + + + + + + + + + {displayName} + + + {profile.email} + + + + + + + + + + + + ); +}; diff --git a/plugins/user-settings/src/components/UserSettings.test.tsx b/plugins/user-settings/src/components/Settings.tsx similarity index 55% rename from plugins/user-settings/src/components/UserSettings.test.tsx rename to plugins/user-settings/src/components/Settings.tsx index 1d43f4093e..ae141457df 100644 --- a/plugins/user-settings/src/components/UserSettings.test.tsx +++ b/plugins/user-settings/src/components/Settings.tsx @@ -14,13 +14,21 @@ * limitations under the License. */ -import * as React from 'react'; -import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; -import { UserSettings } from './UserSettings'; +import React from 'react'; +import { SidebarItem } from '@backstage/core'; +import { SignInAvatar } from './SignInAvatar'; +import { useUserProfile } from './useUserProfileInfo'; +import { settingsRouteRef } from '../plugin'; -describe('', () => { - it('renders', async () => { - const rendered = await renderWithEffects(wrapInTestApp()); - expect(rendered.getByText('G')).toBeInTheDocument(); - }); -}); +export const Settings = () => { + const { displayName } = useUserProfile(); + const SidebarAvatar = () => ; + + return ( + + ); +}; diff --git a/plugins/user-settings/src/components/SettingsDialog.test.tsx b/plugins/user-settings/src/components/SettingsDialog.test.tsx deleted file mode 100644 index fb32e9cfdb..0000000000 --- a/plugins/user-settings/src/components/SettingsDialog.test.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - ApiProvider, - ApiRegistry, - FeatureFlags, - featureFlagsApiRef, -} from '@backstage/core'; -import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; -import { fireEvent } from '@testing-library/react'; -import React from 'react'; -import { SettingsDialog } from './SettingsDialog'; - -const apiRegistry = ApiRegistry.from([ - [featureFlagsApiRef, new FeatureFlags()], -]); - -describe('', () => { - const mockFn = jest.fn(); - - it('displays the users name and email, and the tabs and titles and updates the position', async () => { - const rendered = await renderWithEffects( - wrapInTestApp( - - - , - ), - ); - - expect(rendered.getByText('Guest')).toBeInTheDocument(); - expect(rendered.getByText('guest@example.com')).toBeInTheDocument(); - expect(rendered.getByText('App Settings')).toBeInTheDocument(); - expect(rendered.getByText('Additional Settings')).toBeInTheDocument(); - expect(rendered.getByText('Auth Providers')).toBeInTheDocument(); - expect(rendered.getByText('Feature Flags')).toBeInTheDocument(); - - const flagButton = rendered.getByText('Feature Flags'); - expect(mockFn).toBeCalledTimes(1); - fireEvent.click(flagButton); - expect(mockFn).toBeCalledTimes(2); - }); -}); diff --git a/plugins/user-settings/src/components/SettingsDialog.tsx b/plugins/user-settings/src/components/SettingsDialog.tsx deleted file mode 100644 index c6d9cfa8c5..0000000000 --- a/plugins/user-settings/src/components/SettingsDialog.tsx +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { ChangeEvent, useEffect, useState } from 'react'; -import { Card, CardContent, CardHeader, makeStyles } from '@material-ui/core'; -import { AppSettingsList } from './AppSettingsList'; -import { AuthProvidersList } from './AuthProviderList'; -import { FeatureFlagsList } from './FeatureFlagsList'; -import { SignInAvatar } from './SignInAvatar'; -import { UserSettingsMenu } from './UserSettingsMenu'; -import { useUserProfile } from './useUserProfileInfo'; -import { - useApi, - featureFlagsApiRef, - TabbedCard, - CardTab, - InfoCard, -} from '@backstage/core'; -import { DefaultProviderSettings } from './DefaultProviderSettings'; -import { ProviderSettings } from './UserSettings'; -import { Alert } from '@material-ui/lab'; - -const useStyles = makeStyles({ - root: { - minWidth: 400, - maxWidth: 500, - }, -}); - -type Props = { - providerSettings?: ProviderSettings; - updatePosition: () => void; -}; - -export const SettingsDialog = ({ providerSettings, updatePosition }: Props) => { - const classes = useStyles(); - const { profile, displayName } = useUserProfile(); - const featureFlagsApi = useApi(featureFlagsApiRef); - const featureFlags = featureFlagsApi.getRegisteredFlags(); - const [selectedTab, setSelectedTab] = useState('auth'); - - const providers = providerSettings ?? ; - const ref = React.useRef(updatePosition); - - const handleChange = ( - _ev: ChangeEvent<{}>, - newSelectedTab: string | number, - ) => { - setSelectedTab(newSelectedTab); - }; - - // Update the position of the popover to handle different heights - useEffect(() => { - ref.current?.(); - }, [selectedTab]); - - return ( - - } - action={} - title={displayName} - subheader={profile.email} - /> - - - - - - - - - - {featureFlags.length > 0 ? ( - - ) : ( - // TODO(marcuseide): Replace with empty state component? - No registered Feature Flags found - )} - - - - - ); -}; diff --git a/plugins/user-settings/src/components/SettingsPage.tsx b/plugins/user-settings/src/components/SettingsPage.tsx new file mode 100644 index 0000000000..de76866653 --- /dev/null +++ b/plugins/user-settings/src/components/SettingsPage.tsx @@ -0,0 +1,64 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { useState } from 'react'; +import { + Content, + featureFlagsApiRef, + Header, + HeaderTabs, + Page, + pageTheme, + useApi, +} from '@backstage/core'; +import { General } from './General'; +import { AuthProviders } from './AuthProviders'; +import { DefaultProviderSettings } from './DefaultProviderSettings'; +import { FeatureFlags } from './FeatureFlags'; + +type Props = { + providerSettings?: JSX.Element; +}; + +export const SettingsPage = ({ providerSettings }: Props) => { + const [activeTab, setActiveTab] = useState(0); + const onTabChange = (index: number) => { + setActiveTab(index); + }; + const featureFlagsApi = useApi(featureFlagsApiRef); + const featureFlags = featureFlagsApi.getRegisteredFlags(); + const providers = providerSettings ?? ; + + const tabs = [ + { id: 'general', label: 'General' }, + { id: 'auth-providers', label: 'Authentication Providers' }, + { id: 'feature-flags', label: 'Feature Flags' }, + ]; + + const content = [ + , + , + , + ]; + + return ( + +
+ + {content[activeTab]} + + ); +}; diff --git a/plugins/user-settings/src/components/SignInAvatar.tsx b/plugins/user-settings/src/components/SignInAvatar.tsx index 11a5e6ed99..72c636ffe7 100644 --- a/plugins/user-settings/src/components/SignInAvatar.tsx +++ b/plugins/user-settings/src/components/SignInAvatar.tsx @@ -20,23 +20,21 @@ import { makeStyles, Avatar } from '@material-ui/core'; import { useUserProfile } from './useUserProfileInfo'; import { sidebarConfig } from '@backstage/core'; -const useStyles = makeStyles({ +const useStyles = makeStyles(theme => ({ avatar: { width: ({ size }) => size, height: ({ size }) => size, + fontSize: ({ size }) => size * 0.7, + border: `1px solid ${theme.palette.textSubtle}`, }, -}); +})); type Props = { size?: number }; export const SignInAvatar = ({ size }: Props) => { const { iconSize } = sidebarConfig; const classes = useStyles(size ? { size } : { size: iconSize }); - const { profile, displayName } = useUserProfile(); + const { profile } = useUserProfile(); - return ( - - {displayName[0]} - - ); + return ; }; diff --git a/plugins/user-settings/src/components/ThemeToggle.test.tsx b/plugins/user-settings/src/components/ThemeToggle.test.tsx index df36a985f6..79d9210826 100644 --- a/plugins/user-settings/src/components/ThemeToggle.test.tsx +++ b/plugins/user-settings/src/components/ThemeToggle.test.tsx @@ -23,7 +23,7 @@ import { import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; import { lightTheme } from '@backstage/theme'; import { fireEvent } from '@testing-library/react'; -import * as React from 'react'; +import React from 'react'; import { ThemeToggle } from './ThemeToggle'; const mockTheme = { diff --git a/plugins/user-settings/src/components/UserSettings.tsx b/plugins/user-settings/src/components/UserSettings.tsx deleted file mode 100644 index 34d7fa02be..0000000000 --- a/plugins/user-settings/src/components/UserSettings.tsx +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { useEffect, useContext, useCallback } from 'react'; -import { Popover, PopoverActions } from '@material-ui/core'; -import { SignInAvatar } from './SignInAvatar'; -import { SettingsDialog } from './SettingsDialog'; -import { SidebarItem, SidebarContext } from '@backstage/core'; -import { useUserProfile } from './useUserProfileInfo'; - -export type ProviderSettings = JSX.Element; - -type Props = { - /** - * Available auth providers. Providers will be rendered as children - * of a MUI List. - * - * If undefined, providers that are configured in app-config.yaml - * will be displayed instead. - */ - providerSettings?: ProviderSettings; -}; - -export const UserSettings = ({ providerSettings }: Props) => { - const { isOpen: sidebarOpen } = useContext(SidebarContext); - const { displayName } = useUserProfile(); - const [open, setOpen] = React.useState(false); - const [anchorEl, setAnchorEl] = React.useState( - undefined, - ); - const popoverActionRef = React.useRef(null); - - const updatePosition = useCallback(() => { - popoverActionRef?.current?.updatePosition(); - }, []); - - const handleOpen = (event?: React.MouseEvent) => { - setAnchorEl(event?.currentTarget ?? undefined); - setOpen(true); - }; - - const handleClose = () => { - setAnchorEl(undefined); - setOpen(false); - }; - - useEffect(() => { - if (!sidebarOpen && open) setOpen(false); - }, [open, sidebarOpen]); - - const SidebarAvatar = () => ; - - return ( - <> - - - - - - ); -}; diff --git a/plugins/user-settings/src/components/UserSettingsMenu.test.tsx b/plugins/user-settings/src/components/UserSettingsMenu.test.tsx index b1ad059611..c86d702e30 100644 --- a/plugins/user-settings/src/components/UserSettingsMenu.test.tsx +++ b/plugins/user-settings/src/components/UserSettingsMenu.test.tsx @@ -16,7 +16,7 @@ import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; import { fireEvent } from '@testing-library/react'; -import * as React from 'react'; +import React from 'react'; import { UserSettingsMenu } from './UserSettingsMenu'; describe('', () => { diff --git a/plugins/user-settings/src/index.ts b/plugins/user-settings/src/index.ts index 311ae69a51..113930902a 100644 --- a/plugins/user-settings/src/index.ts +++ b/plugins/user-settings/src/index.ts @@ -14,4 +14,5 @@ * limitations under the License. */ export { plugin } from './plugin'; -export { UserSettings } from './components/UserSettings'; +export { Settings } from './components/Settings'; +export { SettingsPage as Router } from './components/SettingsPage'; diff --git a/plugins/user-settings/src/plugin.ts b/plugins/user-settings/src/plugin.ts index 4b556180f2..2f896c8197 100644 --- a/plugins/user-settings/src/plugin.ts +++ b/plugins/user-settings/src/plugin.ts @@ -13,7 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createPlugin } from '@backstage/core'; +import { createPlugin, createRouteRef } from '@backstage/core'; + +export const settingsRouteRef = createRouteRef({ + path: '/settings', + title: 'Settings', +}); export const plugin = createPlugin({ id: 'user-settings',