Files
Ali Waseem 1c2d28d5b3 chore: wrap local storage into helper methods that are safer (#46628)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

- Noticing our code we have many patterns of calling localstorage and
handling those errors
- We should add those in a single well tested file
- Handle those errors in the singleton which makes it easier for us to
debug customer issues. Logger is outputing local storage warnings for
feature we expose
- Side effect of this is random crashes on studio when local storage
isn't available or handled correctly

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Refactor**
* Improved browser storage handling across the app for more reliable
persistence and graceful behavior in restricted or non-browser
environments (settings, previews, charts, tabs, sign-in/session flows,
integrations, and UI state).

* **New Features**
* Introduced a safe storage layer to standardize and harden
local/session persistence.

* **Tests**
  * Added comprehensive tests covering the new safe storage behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-04 07:41:28 -06:00

89 lines
2.5 KiB
TypeScript

import { LOCAL_STORAGE_KEYS as COMMON_LOCAL_STORAGE_KEYS, safeLocalStorage } from 'common'
import { type ConnectSheetSource } from 'common/telemetry-constants'
import { proxy, snapshot, useSnapshot } from 'valtio'
const getInitialState = () => {
return {
activeDocsSection: ['introduction'],
docsLanguage: 'js',
showProjectApiDocs: false,
showCreateBranchModal: false,
showAiSettingsModal: false,
ongoingQueriesPanelOpen: false,
mobileMenuOpen: false,
showSidebar: true,
showEditorPanel: false,
lastRouteBeforeVisitingAccountPage: '',
}
}
export const appState = proxy({
...getInitialState(),
activeDocsSection: ['introduction'],
docsLanguage: 'js' as 'js' | 'bash',
showProjectApiDocs: false,
setShowProjectApiDocs: (value: boolean) => {
appState.showProjectApiDocs = value
},
setActiveDocsSection: (value: string[]) => {
appState.activeDocsSection = value
},
setDocsLanguage: (value: 'js' | 'bash') => {
appState.docsLanguage = value
},
isOptedInTelemetry: false,
setIsOptedInTelemetry: (value: boolean | null) => {
appState.isOptedInTelemetry = value === null ? false : value
if (value !== null) {
safeLocalStorage.setItem(COMMON_LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT, value.toString())
}
},
isMfaEnforced: false,
setIsMfaEnforced: (value: boolean) => {
appState.isMfaEnforced = value
},
showCreateBranchModal: false,
setShowCreateBranchModal: (value: boolean) => {
appState.showCreateBranchModal = value
},
showAiSettingsModal: false,
setShowAiSettingsModal: (value: boolean) => {
appState.showAiSettingsModal = value
},
showSidebar: true,
setShowSidebar: (value: boolean) => {
appState.showSidebar = value
},
showOngoingQueriesPanelOpen: false,
setOnGoingQueriesPanelOpen: (value: boolean) => {
appState.ongoingQueriesPanelOpen = value
},
mobileMenuOpen: false,
setMobileMenuOpen: (value: boolean) => {
appState.mobileMenuOpen = value
},
connectSheetSource: 'header_button' as ConnectSheetSource,
setConnectSheetSource: (value: ConnectSheetSource) => {
appState.connectSheetSource = value
},
lastRouteBeforeVisitingAccountPage: '',
setLastRouteBeforeVisitingAccountPage: (value: string) => {
appState.lastRouteBeforeVisitingAccountPage = value
},
})
export const getAppStateSnapshot = () => snapshot(appState)
export const useAppStateSnapshot = (options?: Parameters<typeof useSnapshot>[1]) =>
useSnapshot(appState, options)