diff options
| author | Fuwn <[email protected]> | 2026-02-07 01:42:57 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-07 01:42:57 -0800 |
| commit | 5c5b1993edd890a80870ee05607ac5f088191d4e (patch) | |
| tree | a721b76bcd49ba10826c53efc87302c7a689512f /apps/web/lib/stores/user-interface-store.ts | |
| download | asa.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.ts | 135 |
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, + }), + } + ) +) |