mirror of
https://github.com/Rohithgilla12/data-peek
synced 2026-04-21 12:57:16 +00:00
feat(webapp): add SyncProvider and migrate saved queries + history panels to Dexie
This commit is contained in:
parent
e7c72de246
commit
be681f1601
5 changed files with 62 additions and 55 deletions
|
|
@ -3,10 +3,12 @@ import { AppSidebar } from '@/components/sidebar/app-sidebar'
|
|||
import { UsageBanner } from '@/components/upgrade/usage-banner'
|
||||
import { UrlSync } from '@/components/url-sync'
|
||||
import { CommandPalette } from '@/components/command-palette'
|
||||
import { SyncProvider } from '@/components/sync-provider'
|
||||
|
||||
export default function AppLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<Suspense>
|
||||
<SyncProvider>
|
||||
<div className="flex h-screen overflow-hidden">
|
||||
<UrlSync />
|
||||
<CommandPalette />
|
||||
|
|
@ -16,6 +18,7 @@ export default function AppLayout({ children }: { children: React.ReactNode }) {
|
|||
<main className="flex-1 overflow-auto">{children}</main>
|
||||
</div>
|
||||
</div>
|
||||
</SyncProvider>
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
import { useState } from 'react'
|
||||
import { Play, Trash2, CheckCircle, XCircle } from 'lucide-react'
|
||||
import { trpc } from '@/lib/trpc-client'
|
||||
import { useQueryHistory } from '@/hooks/use-query-history'
|
||||
import { useConnectionStore } from '@/stores/connection-store'
|
||||
import { useQueryStore } from '@/stores/query-store'
|
||||
import { useQueryTabs } from '@/hooks/use-query-tabs'
|
||||
|
||||
function formatRelativeTime(date: Date | string): string {
|
||||
const now = Date.now()
|
||||
|
|
@ -23,17 +23,8 @@ function formatRelativeTime(date: Date | string): string {
|
|||
export function QueryHistoryPanel() {
|
||||
const [statusFilter, setStatusFilter] = useState<'success' | 'error' | undefined>()
|
||||
const { activeConnectionId } = useConnectionStore()
|
||||
const { activeTabId, updateSql } = useQueryStore()
|
||||
const utils = trpc.useUtils()
|
||||
|
||||
const { data: entries, isLoading } = trpc.history.list.useQuery(
|
||||
{ connectionId: activeConnectionId ?? undefined, status: statusFilter, limit: 100 },
|
||||
{ enabled: !!activeConnectionId }
|
||||
)
|
||||
|
||||
const deleteMutation = trpc.history.delete.useMutation({
|
||||
onSuccess: () => utils.history.list.invalidate(),
|
||||
})
|
||||
const { activeTabId, updateSql } = useQueryTabs()
|
||||
const { history, remove } = useQueryHistory(activeConnectionId ?? undefined, statusFilter)
|
||||
|
||||
if (!activeConnectionId) {
|
||||
return (
|
||||
|
|
@ -64,11 +55,10 @@ export function QueryHistoryPanel() {
|
|||
</button>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{isLoading && <div className="px-3 py-4 text-xs text-muted-foreground">Loading...</div>}
|
||||
{entries?.length === 0 && (
|
||||
{history.length === 0 && (
|
||||
<div className="px-3 py-4 text-xs text-muted-foreground text-center">No history yet</div>
|
||||
)}
|
||||
{entries?.map((entry) => (
|
||||
{history.map((entry) => (
|
||||
<div
|
||||
key={entry.id}
|
||||
className={`group px-3 py-2 border-b border-border/30 hover:bg-muted/30 ${
|
||||
|
|
@ -90,7 +80,7 @@ export function QueryHistoryPanel() {
|
|||
· {entry.durationMs}ms
|
||||
</span>
|
||||
)}
|
||||
{entry.rowCount !== null && entry.status === 'success' && (
|
||||
{entry.rowCount !== null && entry.rowCount !== undefined && entry.status === 'success' && (
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
· {entry.rowCount} rows
|
||||
</span>
|
||||
|
|
@ -105,7 +95,7 @@ export function QueryHistoryPanel() {
|
|||
<Play className="h-3 w-3" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => deleteMutation.mutate({ id: entry.id })}
|
||||
onClick={() => remove(entry.id)}
|
||||
className="p-1 rounded text-muted-foreground hover:text-destructive"
|
||||
title="Delete"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -4,28 +4,21 @@ import { useState } from 'react'
|
|||
import { Bookmark, X } from 'lucide-react'
|
||||
import { trpc } from '@/lib/trpc-client'
|
||||
import { useConnectionStore } from '@/stores/connection-store'
|
||||
import { useQueryStore } from '@/stores/query-store'
|
||||
import { useQueryTabs } from '@/hooks/use-query-tabs'
|
||||
import { useSavedQueries } from '@/hooks/use-saved-queries'
|
||||
import { ProBadge } from '@/components/upgrade/pro-badge'
|
||||
|
||||
export function SaveQueryDialog() {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [name, setName] = useState('')
|
||||
const [description, setDescription] = useState('')
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
const { activeConnectionId } = useConnectionStore()
|
||||
const { tabs, activeTabId } = useQueryStore()
|
||||
const { tabs, activeTabId } = useQueryTabs()
|
||||
const activeTab = tabs.find((t) => t.id === activeTabId)
|
||||
const utils = trpc.useUtils()
|
||||
const { create } = useSavedQueries()
|
||||
const { data: usage } = trpc.usage.current.useQuery()
|
||||
|
||||
const saveMutation = trpc.savedQueries.create.useMutation({
|
||||
onSuccess: () => {
|
||||
utils.savedQueries.list.invalidate()
|
||||
setOpen(false)
|
||||
setName('')
|
||||
setDescription('')
|
||||
},
|
||||
})
|
||||
|
||||
if (!open) {
|
||||
if (
|
||||
usage?.plan === 'free' &&
|
||||
|
|
@ -63,19 +56,24 @@ export function SaveQueryDialog() {
|
|||
autoFocus
|
||||
/>
|
||||
<button
|
||||
onClick={() => {
|
||||
onClick={async () => {
|
||||
if (!activeConnectionId || !activeTab?.sql || !name.trim()) return
|
||||
saveMutation.mutate({
|
||||
setIsSaving(true)
|
||||
await create({
|
||||
connectionId: activeConnectionId,
|
||||
name: name.trim(),
|
||||
query: activeTab.sql,
|
||||
description: description || undefined,
|
||||
})
|
||||
setIsSaving(false)
|
||||
setOpen(false)
|
||||
setName('')
|
||||
setDescription('')
|
||||
}}
|
||||
disabled={!name.trim() || saveMutation.isPending}
|
||||
disabled={!name.trim() || isSaving}
|
||||
className="rounded-md bg-accent px-2 py-1 text-xs text-accent-foreground hover:bg-accent/90 disabled:opacity-50"
|
||||
>
|
||||
{saveMutation.isPending ? '...' : 'Save'}
|
||||
{isSaving ? '...' : 'Save'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setOpen(false)}
|
||||
|
|
|
|||
|
|
@ -2,27 +2,19 @@
|
|||
|
||||
import { useState } from 'react'
|
||||
import { Play, Trash2, Search } from 'lucide-react'
|
||||
import { trpc } from '@/lib/trpc-client'
|
||||
import { useSavedQueries } from '@/hooks/use-saved-queries'
|
||||
import { useConnectionStore } from '@/stores/connection-store'
|
||||
import { useQueryStore } from '@/stores/query-store'
|
||||
import { useQueryTabs } from '@/hooks/use-query-tabs'
|
||||
|
||||
export function SavedQueriesPanel() {
|
||||
const [search, setSearch] = useState('')
|
||||
const { activeConnectionId } = useConnectionStore()
|
||||
const { activeTabId, updateSql } = useQueryStore()
|
||||
const utils = trpc.useUtils()
|
||||
|
||||
const { data: queries, isLoading } = trpc.savedQueries.list.useQuery(
|
||||
{ connectionId: activeConnectionId ?? undefined, search: search || undefined },
|
||||
{ enabled: !!activeConnectionId }
|
||||
const { activeTabId, updateSql } = useQueryTabs()
|
||||
const { queries, remove, incrementUsage } = useSavedQueries(
|
||||
activeConnectionId ?? undefined,
|
||||
search || undefined
|
||||
)
|
||||
|
||||
const deleteMutation = trpc.savedQueries.delete.useMutation({
|
||||
onSuccess: () => utils.savedQueries.list.invalidate(),
|
||||
})
|
||||
|
||||
const incrementMutation = trpc.savedQueries.incrementUsage.useMutation()
|
||||
|
||||
if (!activeConnectionId) {
|
||||
return (
|
||||
<div className="px-3 py-4 text-xs text-muted-foreground">Select a connection first</div>
|
||||
|
|
@ -44,15 +36,12 @@ export function SavedQueriesPanel() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{isLoading && (
|
||||
<div className="px-3 py-4 text-xs text-muted-foreground">Loading...</div>
|
||||
)}
|
||||
{queries?.length === 0 && (
|
||||
{queries.length === 0 && (
|
||||
<div className="px-3 py-4 text-xs text-muted-foreground text-center">
|
||||
No saved queries
|
||||
</div>
|
||||
)}
|
||||
{queries?.map((q) => (
|
||||
{queries.map((q) => (
|
||||
<div key={q.id} className="group px-3 py-2 border-b border-border/30 hover:bg-muted/30">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs font-medium text-foreground truncate">{q.name}</span>
|
||||
|
|
@ -60,7 +49,7 @@ export function SavedQueriesPanel() {
|
|||
<button
|
||||
onClick={() => {
|
||||
updateSql(activeTabId, q.query)
|
||||
incrementMutation.mutate({ id: q.id })
|
||||
incrementUsage(q.id)
|
||||
}}
|
||||
className="p-1 rounded text-muted-foreground hover:text-accent"
|
||||
title="Load into editor"
|
||||
|
|
@ -68,7 +57,7 @@ export function SavedQueriesPanel() {
|
|||
<Play className="h-3 w-3" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => deleteMutation.mutate({ id: q.id })}
|
||||
onClick={() => remove(q.id)}
|
||||
className="p-1 rounded text-muted-foreground hover:text-destructive"
|
||||
title="Delete"
|
||||
>
|
||||
|
|
|
|||
27
apps/webapp/src/components/sync-provider.tsx
Normal file
27
apps/webapp/src/components/sync-provider.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
'use client'
|
||||
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useAuth } from '@clerk/nextjs'
|
||||
import { trpc, type TRPCClient } from '@/lib/trpc-client'
|
||||
import { SyncManager } from '@/lib/sync-manager'
|
||||
|
||||
export function SyncProvider({ children }: { children: React.ReactNode }) {
|
||||
const { userId } = useAuth()
|
||||
const syncRef = useRef<SyncManager | null>(null)
|
||||
const trpcClient = trpc.useUtils().client as TRPCClient
|
||||
|
||||
useEffect(() => {
|
||||
if (!userId || !trpcClient) return
|
||||
|
||||
const manager = new SyncManager(userId, trpcClient)
|
||||
syncRef.current = manager
|
||||
manager.start()
|
||||
|
||||
return () => {
|
||||
manager.stop()
|
||||
syncRef.current = null
|
||||
}
|
||||
}, [userId, trpcClient])
|
||||
|
||||
return <>{children}</>
|
||||
}
|
||||
Loading…
Reference in a new issue