mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
Feat/UI feature previews (#17775)
* Break me.tsx into smaller components * Deprecate duplicate app valtio state * Initialize feature preview modal * Add telemetry * Address comments
This commit is contained in:
1 parent
bc1c9090c2
commit
32dda1bb23
15 files changed
+332
-137
No files matched your search
@@ -0,0 +1,57 @@
|
||||
import { Button, Input } from 'ui'
|
||||
import Link from 'next/link'
|
||||
|
||||
import Panel from 'components/ui/Panel'
|
||||
import { useSession } from 'lib/auth'
|
||||
import { Profile } from 'data/profile/types'
|
||||
|
||||
const AccountInformation = ({ profile }: { profile?: Profile }) => {
|
||||
const session = useSession()
|
||||
|
||||
return (
|
||||
<Panel
|
||||
title={
|
||||
<h5 key="panel-title" className="mb-0">
|
||||
Account Information
|
||||
</h5>
|
||||
}
|
||||
>
|
||||
<Panel.Content>
|
||||
<div className="space-y-2">
|
||||
<Input
|
||||
readOnly
|
||||
disabled
|
||||
label="Username"
|
||||
layout="horizontal"
|
||||
value={profile?.username ?? ''}
|
||||
/>
|
||||
<Input
|
||||
readOnly
|
||||
disabled
|
||||
label="Email"
|
||||
layout="horizontal"
|
||||
value={profile?.primary_email ?? ''}
|
||||
/>
|
||||
{session?.user.app_metadata.provider === 'email' && (
|
||||
<div className="text-sm grid gap-2 md:grid md:grid-cols-12 md:gap-x-4">
|
||||
<div className="flex flex-col space-y-2 col-span-4 ">
|
||||
<p className="text-foreground-light break-all">Password</p>
|
||||
</div>
|
||||
<div className="col-span-8">
|
||||
<Link href="/reset-password">
|
||||
<a>
|
||||
<Button type="default" size="medium">
|
||||
Reset password
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Panel.Content>
|
||||
</Panel>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountInformation
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Toggle } from 'ui'
|
||||
|
||||
import Panel from 'components/ui/Panel'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
|
||||
const AnalyticsSettings = () => {
|
||||
const snap = useAppStateSnapshot()
|
||||
|
||||
const onToggleOptIn = () => {
|
||||
const value = !snap.isOptedInTelemetry ? 'true' : 'false'
|
||||
snap.setIsOptedInTelemetry(value === 'true')
|
||||
}
|
||||
|
||||
return (
|
||||
<Panel title={<h5 key="panel-title">Analytics</h5>}>
|
||||
<Panel.Content>
|
||||
<Toggle
|
||||
checked={snap.isOptedInTelemetry}
|
||||
onChange={onToggleOptIn}
|
||||
label="Opt-in to send telemetry data from the dashboard"
|
||||
descriptionText="By opting into sending telemetry data, Supabase can improve the overall dashboard user experience"
|
||||
/>
|
||||
</Panel.Content>
|
||||
</Panel>
|
||||
)
|
||||
}
|
||||
|
||||
export default AnalyticsSettings
|
||||
@@ -0,0 +1,36 @@
|
||||
import { useTheme } from 'next-themes'
|
||||
import { IconMoon, IconSun, Listbox } from 'ui'
|
||||
|
||||
import Panel from 'components/ui/Panel'
|
||||
|
||||
const ThemeSettings = () => {
|
||||
const { theme, setTheme } = useTheme()
|
||||
|
||||
return (
|
||||
<Panel title={<h5 key="panel-title">Theme</h5>}>
|
||||
<Panel.Content>
|
||||
<Listbox
|
||||
value={theme}
|
||||
label="Interface theme"
|
||||
descriptionText="Choose a theme preference"
|
||||
layout="horizontal"
|
||||
style={{ width: '50%' }}
|
||||
icon={theme ? <IconMoon /> : <IconSun />}
|
||||
onChange={(value: string) => setTheme(value)}
|
||||
>
|
||||
<Listbox.Option label="System default" value="system">
|
||||
System default
|
||||
</Listbox.Option>
|
||||
<Listbox.Option label="Dark" value="dark">
|
||||
Dark
|
||||
</Listbox.Option>
|
||||
<Listbox.Option label="Light" value="light">
|
||||
Light
|
||||
</Listbox.Option>
|
||||
</Listbox>
|
||||
</Panel.Content>
|
||||
</Panel>
|
||||
)
|
||||
}
|
||||
|
||||
export default ThemeSettings
|
||||
@@ -0,0 +1,5 @@
|
||||
import AccountInformation from './AccountInformation'
|
||||
import AnalyticsSettings from './AnalyticsSettings'
|
||||
import ThemeSettings from './ThemeSettings'
|
||||
|
||||
export { AccountInformation, AnalyticsSettings, ThemeSettings }
|
||||
@@ -0,0 +1,61 @@
|
||||
import { noop } from 'lodash'
|
||||
import { PropsWithChildren, createContext, useContext, useEffect, useState } from 'react'
|
||||
|
||||
import { LOCAL_STORAGE_KEYS } from 'lib/constants'
|
||||
import { EMPTY_OBJ } from 'lib/void'
|
||||
|
||||
type FeaturePreviewContextType = {
|
||||
flags: { [key: string]: boolean }
|
||||
onUpdateFlag: (key: string, value: boolean) => void
|
||||
}
|
||||
|
||||
const FeaturePreviewContext = createContext<FeaturePreviewContextType>({
|
||||
flags: EMPTY_OBJ,
|
||||
onUpdateFlag: noop,
|
||||
})
|
||||
|
||||
export default FeaturePreviewContext
|
||||
|
||||
export const useFeaturePreviewContext = () => useContext(FeaturePreviewContext)
|
||||
|
||||
export const FeaturePreviewContextProvider = ({ children }: PropsWithChildren<{}>) => {
|
||||
const [flags, setFlags] = useState({
|
||||
[LOCAL_STORAGE_KEYS.UI_PREVIEW_NAVIGATION_LAYOUT]: false,
|
||||
[LOCAL_STORAGE_KEYS.UI_PREVIEW_API_SIDE_PANEL]: false,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
setFlags({
|
||||
[LOCAL_STORAGE_KEYS.UI_PREVIEW_NAVIGATION_LAYOUT]:
|
||||
localStorage.getItem(LOCAL_STORAGE_KEYS.UI_PREVIEW_NAVIGATION_LAYOUT) === 'true',
|
||||
[LOCAL_STORAGE_KEYS.UI_PREVIEW_API_SIDE_PANEL]:
|
||||
localStorage.getItem(LOCAL_STORAGE_KEYS.UI_PREVIEW_API_SIDE_PANEL) === 'true',
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
const value = {
|
||||
flags,
|
||||
onUpdateFlag: (key: string, value: boolean) => {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.localStorage.setItem(key, value.toString())
|
||||
}
|
||||
const updatedFlags = { ...flags, [key]: value }
|
||||
setFlags(updatedFlags)
|
||||
},
|
||||
}
|
||||
|
||||
return <FeaturePreviewContext.Provider value={value}>{children}</FeaturePreviewContext.Provider>
|
||||
}
|
||||
|
||||
// Helpers
|
||||
export const useIsNavigationPreviewEnabled = () => {
|
||||
const { flags } = useFeaturePreviewContext()
|
||||
return flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_NAVIGATION_LAYOUT]
|
||||
}
|
||||
|
||||
export const useIsAPIDocsSidePanelEnabled = () => {
|
||||
const { flags } = useFeaturePreviewContext()
|
||||
return flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_API_SIDE_PANEL]
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { useTelemetryProps } from 'common'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useState } from 'react'
|
||||
import { Button, IconEye, IconEyeOff, Modal, ScrollArea, cn } from 'ui'
|
||||
|
||||
import { LOCAL_STORAGE_KEYS } from 'lib/constants'
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import { Markdown } from '../Markdown'
|
||||
import { useFeaturePreviewContext } from './FeaturePreviewContext'
|
||||
|
||||
// [Ivan] We should probably move this to a separate file, together with LOCAL_STORAGE_KEYS. We should make adding new feature previews as simple as possible.
|
||||
|
||||
const FEATURE_PREVIEWS = [
|
||||
// {
|
||||
// key: LOCAL_STORAGE_KEYS.UI_PREVIEW_NAVIGATION_LAYOUT,
|
||||
// name: 'Global navigation update',
|
||||
// description: `Experience a redesigned and improved site navigation on the dashboard, with an intention to making finding your way around more intuitive and easier.`,
|
||||
// },
|
||||
{
|
||||
key: LOCAL_STORAGE_KEYS.UI_PREVIEW_API_SIDE_PANEL,
|
||||
name: 'Project API documentation',
|
||||
description: `Get building with your client application faster with your Project's API documentation now available on any page across the dashboard.`,
|
||||
},
|
||||
]
|
||||
|
||||
const FeaturePreviewModal = () => {
|
||||
const router = useRouter()
|
||||
const snap = useAppStateSnapshot()
|
||||
const telemetryProps = useTelemetryProps()
|
||||
const featurePreviewContext = useFeaturePreviewContext()
|
||||
const [selectedFeatureKey, setSelectedFeatureKey] = useState<string>(FEATURE_PREVIEWS[0].key)
|
||||
|
||||
const { flags, onUpdateFlag } = featurePreviewContext
|
||||
const selectedFeature = FEATURE_PREVIEWS.find((preview) => preview.key === selectedFeatureKey)
|
||||
const isSelectedFeatureEnabled = flags[selectedFeatureKey]
|
||||
|
||||
const toggleFeature = () => {
|
||||
onUpdateFlag(selectedFeatureKey, !isSelectedFeatureEnabled)
|
||||
Telemetry.sendEvent(
|
||||
{
|
||||
category: 'ui_feature_previews',
|
||||
action: isSelectedFeatureEnabled ? 'disabled' : 'enabled',
|
||||
label: selectedFeatureKey,
|
||||
},
|
||||
telemetryProps,
|
||||
router
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
hideFooter
|
||||
size="xlarge"
|
||||
header="Dashboard feature previews"
|
||||
visible={snap.showFeaturePreviewModal}
|
||||
onCancel={() => snap.setShowFeaturePreviewModal(false)}
|
||||
>
|
||||
<div className="flex">
|
||||
<div>
|
||||
<ScrollArea className="h-[400px] w-[240px] border-r">
|
||||
{FEATURE_PREVIEWS.map((feature) => {
|
||||
const isEnabled = flags[feature.key] ?? false
|
||||
|
||||
return (
|
||||
<div
|
||||
key={feature.key}
|
||||
onClick={() => setSelectedFeatureKey(feature.key)}
|
||||
className={cn(
|
||||
'flex items-center space-x-3 p-4 border-b cursor-pointer bg transition',
|
||||
selectedFeatureKey === feature.key ? 'bg-surface-200' : ''
|
||||
)}
|
||||
>
|
||||
{isEnabled ? (
|
||||
<IconEye size={14} strokeWidth={2} className="text-brand" />
|
||||
) : (
|
||||
<IconEyeOff size={14} strokeWidth={1.5} className="text-foreground-light" />
|
||||
)}
|
||||
<p className="text-sm">{feature.name}</p>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
<div className="flex-grow p-4 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<p>{selectedFeature?.name}</p>
|
||||
<Button type="default" onClick={() => toggleFeature()}>
|
||||
{isSelectedFeatureEnabled ? 'Disable' : 'Enable'} feature
|
||||
</Button>
|
||||
</div>
|
||||
<Markdown className="text-sm" content={selectedFeature?.description ?? ''} />
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default FeaturePreviewModal
|
||||
@@ -10,8 +10,6 @@ import {
|
||||
Button,
|
||||
IconAlertTriangle,
|
||||
IconGitBranch,
|
||||
IconSearch,
|
||||
Input,
|
||||
Modal,
|
||||
} from 'ui'
|
||||
|
||||
@@ -27,7 +25,7 @@ import { Branch, useBranchesQuery } from 'data/branches/branches-query'
|
||||
import { useOrgIntegrationsQuery } from 'data/integrations/integrations-query-org-only'
|
||||
import { useSelectedOrganization, useSelectedProject, useStore } from 'hooks'
|
||||
import { OPT_IN_TAGS } from 'lib/constants'
|
||||
import { useAppUiStateSnapshot } from 'state/app'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import { MainBranchPanel } from './BranchPanels'
|
||||
import CreateBranchSidePanel from './CreateBranchSidePanel'
|
||||
import PreviewBranches from './PreviewBranches'
|
||||
@@ -48,7 +46,7 @@ const BranchManagement = () => {
|
||||
const projectRef =
|
||||
project !== undefined ? (isBranch ? project.parent_project_ref : ref) : undefined
|
||||
|
||||
const snap = useAppUiStateSnapshot()
|
||||
const snap = useAppStateSnapshot()
|
||||
const [showCreateBranch, setShowCreateBranch] = useState(false)
|
||||
const [showDisableBranching, setShowDisableBranching] = useState(false)
|
||||
const [selectedBranchToDelete, setSelectedBranchToDelete] = useState<Branch>()
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { Button, IconGitBranch } from 'ui'
|
||||
|
||||
import { useSelectedOrganization } from 'hooks'
|
||||
import { useAppUiStateSnapshot } from 'state/app'
|
||||
import BranchingWaitlistPopover from './BranchingWaitlistPopover'
|
||||
|
||||
import { OPT_IN_TAGS } from 'lib/constants'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
|
||||
interface EnableBranchingButtonProps {
|
||||
isNewNav?: boolean
|
||||
}
|
||||
|
||||
const EnableBranchingButton = ({ isNewNav = false }: EnableBranchingButtonProps) => {
|
||||
const snap = useAppUiStateSnapshot()
|
||||
const snap = useAppStateSnapshot()
|
||||
const selectedOrg = useSelectedOrganization()
|
||||
|
||||
const hasAccessToBranching =
|
||||
|
||||
@@ -9,17 +9,15 @@ import { useBranchCreateMutation } from 'data/branches/branch-create-mutation'
|
||||
import { useOrgIntegrationsQuery } from 'data/integrations/integrations-query-org-only'
|
||||
import { IntegrationName } from 'data/integrations/integrations.types'
|
||||
import { useSelectedOrganization, useStore } from 'hooks'
|
||||
import { useAppUiStateSnapshot } from 'state/app'
|
||||
import BranchingWaitlistPopover from './BranchingWaitlistPopover'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import GithubRepositorySelection from './GithubRepositorySelection'
|
||||
import VercelProjectSelection from './VercelProjectSelection'
|
||||
|
||||
const EnableBranchingModal = () => {
|
||||
const { ui } = useStore()
|
||||
const { ref } = useParams()
|
||||
const selectedOrg = useSelectedOrganization()
|
||||
|
||||
const snap = useAppUiStateSnapshot()
|
||||
const snap = useAppStateSnapshot()
|
||||
const [selectedBranch, setSelectedBranch] = useState<string>()
|
||||
const [addConnectionType, setAddConnectionType] = useState<IntegrationName>()
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useFlag } from 'hooks'
|
||||
import { IS_PLATFORM } from 'lib/constants'
|
||||
import { detectOS } from 'lib/helpers'
|
||||
import { isUndefined } from 'lodash'
|
||||
import { FlaskConical } from 'lucide-react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
@@ -32,23 +33,26 @@ import {
|
||||
generateToolRoutes,
|
||||
} from './NavigationBar.utils'
|
||||
import NavigationIconButton from './NavigationIconButton'
|
||||
import ThemeToggle from '@ui/components/ThemeProvider/ThemeToggle'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
|
||||
const NavigationBar = () => {
|
||||
const os = detectOS()
|
||||
const router = useRouter()
|
||||
const snap = useAppStateSnapshot()
|
||||
const { theme, setTheme } = useTheme()
|
||||
const { ref: projectRef } = useParams()
|
||||
const os = detectOS()
|
||||
const { setIsOpen } = useCommandMenu()
|
||||
|
||||
const { project } = useProjectContext()
|
||||
const navLayoutV2 = useFlag('navigationLayoutV2')
|
||||
const supabaseAIEnabled = useFlag('sqlEditorSupabaseAI')
|
||||
const showFeaturePreviews = useFlag('featurePreviews')
|
||||
|
||||
const activeRoute = router.pathname.split('/')[3]
|
||||
const toolRoutes = generateToolRoutes(projectRef, project, supabaseAIEnabled)
|
||||
const productRoutes = generateProductRoutes(projectRef, project)
|
||||
const otherRoutes = generateOtherRoutes(projectRef, project)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
@@ -153,9 +157,20 @@ const NavigationBar = () => {
|
||||
<Link href="/account/me">
|
||||
<DropdownMenuItem_Shadcn_ key="header" className="space-x-2">
|
||||
<IconSettings size={14} strokeWidth={1.5} />
|
||||
<p className="text">Account Preferences</p>
|
||||
<p className="text">Account preferences</p>
|
||||
</DropdownMenuItem_Shadcn_>
|
||||
</Link>
|
||||
{showFeaturePreviews && (
|
||||
<DropdownMenuItem_Shadcn_
|
||||
key="header"
|
||||
className="space-x-2"
|
||||
onClick={() => snap.setShowFeaturePreviewModal(true)}
|
||||
onSelect={() => snap.setShowFeaturePreviewModal(true)}
|
||||
>
|
||||
<FlaskConical size={14} strokeWidth={2} />
|
||||
<p className="text">Feature previews</p>
|
||||
</DropdownMenuItem_Shadcn_>
|
||||
)}
|
||||
<DropdownMenuSeparator_Shadcn_ />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -96,6 +96,8 @@ export const USAGE_APPROACHING_THRESHOLD = 0.75
|
||||
export const LOCAL_STORAGE_KEYS = {
|
||||
RECENTLY_VISITED_ORGANIZATION: 'supabase-organization',
|
||||
TELEMETRY_CONSENT: 'supabase-consent',
|
||||
UI_PREVIEW_NAVIGATION_LAYOUT: 'supabase-ui-preview-nav-layout',
|
||||
UI_PREVIEW_API_SIDE_PANEL: 'supabase-ui-api-side-panel',
|
||||
}
|
||||
|
||||
export const OPT_IN_TAGS = {
|
||||
|
||||
@@ -55,6 +55,8 @@ import { useAppStateSnapshot } from 'state/app-state'
|
||||
import { RootStore } from 'stores'
|
||||
import HCaptchaLoadedStore from 'stores/hcaptcha-loaded-store'
|
||||
import { AppPropsWithLayout } from 'types'
|
||||
import { FeaturePreviewContextProvider } from 'components/interfaces/App/FeaturePreviewContext'
|
||||
import FeaturePreviewModal from 'components/interfaces/App/FeaturePreviewModal'
|
||||
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
@@ -192,7 +194,10 @@ function CustomApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||
>
|
||||
<CommandMenuWrapper>
|
||||
<AppBannerWrapper>
|
||||
{getLayout(<Component {...pageProps} />)}
|
||||
<FeaturePreviewContextProvider>
|
||||
{getLayout(<Component {...pageProps} />)}
|
||||
<FeaturePreviewModal />
|
||||
</FeaturePreviewContextProvider>
|
||||
</AppBannerWrapper>
|
||||
</CommandMenuWrapper>
|
||||
</ThemeProvider>
|
||||
|
||||
+6
-109
@@ -1,21 +1,19 @@
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import Link from 'next/link'
|
||||
|
||||
import { useTheme } from 'next-themes'
|
||||
|
||||
import {
|
||||
AccountInformation,
|
||||
AnalyticsSettings,
|
||||
ThemeSettings,
|
||||
} from 'components/interfaces/Account/Preferences'
|
||||
import { AccountLayout } from 'components/layouts'
|
||||
import SchemaFormPanel from 'components/to-be-cleaned/forms/SchemaFormPanel'
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
import Panel from 'components/ui/Panel'
|
||||
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
|
||||
import { useProfileUpdateMutation } from 'data/profile/profile-update-mutation'
|
||||
import { Profile as ProfileType } from 'data/profile/types'
|
||||
import { useStore } from 'hooks'
|
||||
import { useSession } from 'lib/auth'
|
||||
import { useProfile } from 'lib/profile'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import { NextPageWithLayout } from 'types'
|
||||
import { Button, IconMoon, IconSun, Input, Listbox, Toggle } from 'ui'
|
||||
|
||||
const User: NextPageWithLayout = () => {
|
||||
return (
|
||||
@@ -86,7 +84,7 @@ const ProfileCard = observer(() => {
|
||||
{isSuccess && (
|
||||
<>
|
||||
<section>
|
||||
<Profile profile={profile} />
|
||||
<AccountInformation profile={profile} />
|
||||
</section>
|
||||
<section>
|
||||
{/* @ts-ignore */}
|
||||
@@ -121,104 +119,3 @@ const ProfileCard = observer(() => {
|
||||
</article>
|
||||
)
|
||||
})
|
||||
|
||||
const Profile = ({ profile }: { profile?: ProfileType }) => {
|
||||
const session = useSession()
|
||||
|
||||
return (
|
||||
<Panel
|
||||
title={
|
||||
<h5 key="panel-title" className="mb-0">
|
||||
Account Information
|
||||
</h5>
|
||||
}
|
||||
>
|
||||
<Panel.Content>
|
||||
<div className="space-y-2">
|
||||
<Input
|
||||
readOnly
|
||||
disabled
|
||||
label="Username"
|
||||
layout="horizontal"
|
||||
value={profile?.username ?? ''}
|
||||
/>
|
||||
<Input
|
||||
readOnly
|
||||
disabled
|
||||
label="Email"
|
||||
layout="horizontal"
|
||||
value={profile?.primary_email ?? ''}
|
||||
/>
|
||||
{session?.user.app_metadata.provider === 'email' && (
|
||||
<div className="text-sm grid gap-2 md:grid md:grid-cols-12 md:gap-x-4">
|
||||
<div className="flex flex-col space-y-2 col-span-4 ">
|
||||
<p className="text-foreground-light break-all">Password</p>
|
||||
</div>
|
||||
<div className="col-span-8">
|
||||
<Link href="/reset-password">
|
||||
<a>
|
||||
<Button type="default" size="medium">
|
||||
Reset password
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Panel.Content>
|
||||
</Panel>
|
||||
)
|
||||
}
|
||||
|
||||
const ThemeSettings = observer(() => {
|
||||
const { theme, setTheme } = useTheme()
|
||||
|
||||
return (
|
||||
<Panel title={<h5 key="panel-title">Theme</h5>}>
|
||||
<Panel.Content>
|
||||
<Listbox
|
||||
value={theme}
|
||||
label="Interface theme"
|
||||
descriptionText="Choose a theme preference"
|
||||
layout="horizontal"
|
||||
style={{ width: '50%' }}
|
||||
icon={theme ? <IconMoon /> : <IconSun />}
|
||||
onChange={(value: string) => setTheme(value)}
|
||||
>
|
||||
<Listbox.Option label="System default" value="system">
|
||||
System default
|
||||
</Listbox.Option>
|
||||
<Listbox.Option label="Dark" value="dark">
|
||||
Dark
|
||||
</Listbox.Option>
|
||||
<Listbox.Option label="Light" value="light">
|
||||
Light
|
||||
</Listbox.Option>
|
||||
</Listbox>
|
||||
</Panel.Content>
|
||||
</Panel>
|
||||
)
|
||||
})
|
||||
|
||||
const AnalyticsSettings = observer(() => {
|
||||
const snap = useAppStateSnapshot()
|
||||
|
||||
const onToggleOptIn = () => {
|
||||
const value = !snap.isOptedInTelemetry ? 'true' : 'false'
|
||||
snap.setIsOptedInTelemetry(value === 'true')
|
||||
}
|
||||
|
||||
return (
|
||||
<Panel title={<h5 key="panel-title">Analytics</h5>}>
|
||||
<Panel.Content>
|
||||
<Toggle
|
||||
checked={snap.isOptedInTelemetry}
|
||||
onChange={onToggleOptIn}
|
||||
label="Opt-in to send telemetry data from the dashboard"
|
||||
descriptionText="By opting into sending telemetry data, Supabase can improve the overall dashboard user experience"
|
||||
/>
|
||||
</Panel.Content>
|
||||
</Panel>
|
||||
)
|
||||
})
|
||||
@@ -9,6 +9,14 @@ export const appState = proxy({
|
||||
localStorage.setItem(LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT, value.toString())
|
||||
}
|
||||
},
|
||||
showEnableBranchingModal: false,
|
||||
setShowEnableBranchingModal: (value: boolean) => {
|
||||
appState.showEnableBranchingModal = value
|
||||
},
|
||||
showFeaturePreviewModal: false,
|
||||
setShowFeaturePreviewModal: (value: boolean) => {
|
||||
appState.showFeaturePreviewModal = value
|
||||
},
|
||||
})
|
||||
|
||||
export const getAppStateSnapshot = () => snapshot(appState)
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { proxy, snapshot, useSnapshot } from 'valtio'
|
||||
|
||||
export const appUiState = proxy({
|
||||
showEnableBranchingModal: false,
|
||||
|
||||
setShowEnableBranchingModal: (value: boolean) => {
|
||||
appUiState.showEnableBranchingModal = value
|
||||
},
|
||||
})
|
||||
|
||||
export const getAppUiStateSnapshot = () => snapshot(appUiState)
|
||||
|
||||
export const useAppUiStateSnapshot = (options?: Parameters<typeof useSnapshot>[1]) =>
|
||||
useSnapshot(appUiState, options)
|
||||
Reference in new issue
Block a user