summaryrefslogtreecommitdiff
path: root/apps/web/lib/stores/user-interface-store.ts
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-07 01:42:57 -0800
committerFuwn <[email protected]>2026-02-07 01:42:57 -0800
commit5c5b1993edd890a80870ee05607ac5f088191d4e (patch)
treea721b76bcd49ba10826c53efc87302c7a689512f /apps/web/lib/stores/user-interface-store.ts
downloadasa.news-5c5b1993edd890a80870ee05607ac5f088191d4e.tar.xz
asa.news-5c5b1993edd890a80870ee05607ac5f088191d4e.zip
feat: asa.news RSS reader with developer tier, REST API, and webhooks
Full-stack RSS reader SaaS: Supabase + Next.js + Go worker. Includes three subscription tiers (free/pro/developer), API key auth, read-only REST API, webhook push notifications, Stripe billing with proration, and PWA support.
Diffstat (limited to 'apps/web/lib/stores/user-interface-store.ts')
-rw-r--r--apps/web/lib/stores/user-interface-store.ts135
1 files changed, 135 insertions, 0 deletions
diff --git a/apps/web/lib/stores/user-interface-store.ts b/apps/web/lib/stores/user-interface-store.ts
new file mode 100644
index 0000000..468542d
--- /dev/null
+++ b/apps/web/lib/stores/user-interface-store.ts
@@ -0,0 +1,135 @@
+import { create } from "zustand"
+import { persist } from "zustand/middleware"
+
+type EntryListViewMode = "compact" | "comfortable" | "expanded"
+
+type DisplayDensity = "compact" | "default" | "spacious"
+
+type FocusedPanel = "sidebar" | "entryList" | "detailPanel"
+
+type SettingsTab =
+ | "subscriptions"
+ | "folders"
+ | "muted-keywords"
+ | "custom-feeds"
+ | "import-export"
+ | "appearance"
+ | "account"
+ | "security"
+ | "billing"
+ | "api"
+ | "danger"
+
+interface UserInterfaceState {
+ isSidebarCollapsed: boolean
+ isCommandPaletteOpen: boolean
+ isAddFeedDialogOpen: boolean
+ isSearchOpen: boolean
+ selectedEntryIdentifier: string | null
+ focusedEntryIdentifier: string | null
+ focusedPanel: FocusedPanel
+ focusedSidebarIndex: number
+ entryListViewMode: EntryListViewMode
+ displayDensity: DisplayDensity
+ activeSettingsTab: SettingsTab
+ showFeedFavicons: boolean
+ focusFollowsInteraction: boolean
+ expandedFolderIdentifiers: string[]
+ navigableEntryIdentifiers: string[]
+
+ toggleSidebar: () => void
+ setSidebarCollapsed: (isCollapsed: boolean) => void
+ setCommandPaletteOpen: (isOpen: boolean) => void
+ setAddFeedDialogOpen: (isOpen: boolean) => void
+ setSearchOpen: (isOpen: boolean) => void
+ setSelectedEntryIdentifier: (identifier: string | null) => void
+ setFocusedEntryIdentifier: (identifier: string | null) => void
+ setFocusedPanel: (panel: FocusedPanel) => void
+ setFocusedSidebarIndex: (index: number) => void
+ setEntryListViewMode: (mode: EntryListViewMode) => void
+ setDisplayDensity: (density: DisplayDensity) => void
+ setActiveSettingsTab: (tab: SettingsTab) => void
+ setShowFeedFavicons: (show: boolean) => void
+ setFocusFollowsInteraction: (enabled: boolean) => void
+ toggleFolderExpansion: (folderIdentifier: string) => void
+ setNavigableEntryIdentifiers: (identifiers: string[]) => void
+}
+
+export const useUserInterfaceStore = create<UserInterfaceState>()(
+ persist(
+ (set) => ({
+ isSidebarCollapsed: false,
+ isCommandPaletteOpen: false,
+ isAddFeedDialogOpen: false,
+ isSearchOpen: false,
+ selectedEntryIdentifier: null,
+ focusedEntryIdentifier: null,
+ focusedPanel: "entryList",
+ focusedSidebarIndex: 0,
+ entryListViewMode: "comfortable",
+ displayDensity: "default",
+ activeSettingsTab: "subscriptions",
+ showFeedFavicons: true,
+ focusFollowsInteraction: false,
+ expandedFolderIdentifiers: [],
+ navigableEntryIdentifiers: [],
+
+ toggleSidebar: () =>
+ set((state) => ({ isSidebarCollapsed: !state.isSidebarCollapsed })),
+
+ setSidebarCollapsed: (isCollapsed) =>
+ set({ isSidebarCollapsed: isCollapsed }),
+
+ setCommandPaletteOpen: (isOpen) => set({ isCommandPaletteOpen: isOpen }),
+
+ setAddFeedDialogOpen: (isOpen) => set({ isAddFeedDialogOpen: isOpen }),
+
+ setSearchOpen: (isOpen) => set({ isSearchOpen: isOpen }),
+
+ setSelectedEntryIdentifier: (identifier) =>
+ set({ selectedEntryIdentifier: identifier }),
+
+ setFocusedEntryIdentifier: (identifier) =>
+ set({ focusedEntryIdentifier: identifier }),
+
+ setFocusedPanel: (panel) => set({ focusedPanel: panel }),
+
+ setFocusedSidebarIndex: (index) => set({ focusedSidebarIndex: index }),
+
+ setEntryListViewMode: (mode) => set({ entryListViewMode: mode }),
+
+ setDisplayDensity: (density) => set({ displayDensity: density }),
+
+ setActiveSettingsTab: (tab) => set({ activeSettingsTab: tab }),
+
+ setShowFeedFavicons: (show) => set({ showFeedFavicons: show }),
+
+ setFocusFollowsInteraction: (enabled) =>
+ set({ focusFollowsInteraction: enabled }),
+
+ toggleFolderExpansion: (folderIdentifier) =>
+ set((state) => {
+ const current = state.expandedFolderIdentifiers
+ const isExpanded = current.includes(folderIdentifier)
+ return {
+ expandedFolderIdentifiers: isExpanded
+ ? current.filter((id) => id !== folderIdentifier)
+ : [...current, folderIdentifier],
+ }
+ }),
+
+ setNavigableEntryIdentifiers: (identifiers) =>
+ set({ navigableEntryIdentifiers: identifiers }),
+ }),
+ {
+ name: "asa-news-ui-preferences",
+ partialize: (state) => ({
+ entryListViewMode: state.entryListViewMode,
+ displayDensity: state.displayDensity,
+ showFeedFavicons: state.showFeedFavicons,
+ focusFollowsInteraction: state.focusFollowsInteraction,
+ expandedFolderIdentifiers: state.expandedFolderIdentifiers,
+ }),
+ }
+ )
+)