Skip to content

Commit cee89a9

Browse files
authored
added support for database (#15)
1 parent bcd3a27 commit cee89a9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2853
-4880
lines changed

CLAUDE.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,7 @@ interface Block {
130130
```
131131

132132
Key files:
133-
- `components/editor/SnapDocsEditor.tsx` - Main editor with drag-and-drop
134-
- `components/editor/blocks/` - Individual block implementations
133+
- `components/editor/BlockNoteEditor.tsx` - Main editor using BlockNote
135134
- `lib/services/page-content.ts` - MongoDB content service
136135

137136
### Real-Time Collaboration
@@ -175,11 +174,12 @@ Required in `.env.local`:
175174

176175
## Development Tips
177176

178-
### Adding New Block Types
179-
1. Add type to `BlockType` union in `types/index.ts`
180-
2. Create block component in `components/editor/blocks/`
181-
3. Update `BlockV2.tsx` to handle rendering
182-
4. Add to slash menu in `SlashMenu.tsx`
177+
### Editor
178+
The application uses BlockNote as the main editor, which provides:
179+
- Rich text editing with slash commands
180+
- Built-in block types (headings, lists, tables, etc.)
181+
- File uploads and media embedding
182+
- Real-time collaboration support
183183

184184
### Database Operations
185185
```typescript

app/(protected)/ClientLayout.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
'use client'
22

3-
import { SocketProvider } from '@/lib/socket/client'
4-
53
export function ClientLayout({ children }: { children: React.ReactNode }) {
64
return (
7-
<SocketProvider>
5+
<>
86
{children}
9-
</SocketProvider>
7+
</>
108
)
119
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
'use client'
2+
3+
import { useState } from 'react'
4+
import { useRouter } from 'next/navigation'
5+
import { CreateDatabaseDialog } from '@/components/database'
6+
import { Button } from '@/components/ui/button'
7+
import { ArrowLeft, Database } from 'lucide-react'
8+
import Link from 'next/link'
9+
import toast from 'react-hot-toast'
10+
11+
interface CreateDatabasePageProps {
12+
workspaceId: string
13+
workspace: {
14+
id: string
15+
name: string
16+
slug: string
17+
}
18+
user: {
19+
id: string
20+
email: string
21+
name?: string | null
22+
}
23+
}
24+
25+
export default function CreateDatabasePage({ workspaceId, workspace, user }: CreateDatabasePageProps) {
26+
const router = useRouter()
27+
const [showDialog, setShowDialog] = useState(true)
28+
29+
const handleCreateDatabase = async (database: any) => {
30+
toast.success('Database created successfully!')
31+
router.push(`/workspace/${workspaceId}/database/${database.id}`)
32+
}
33+
34+
const handleCancel = () => {
35+
router.push(`/workspace/${workspaceId}`)
36+
}
37+
38+
return (
39+
<div className="min-h-screen bg-gray-50">
40+
<div className="max-w-7xl mx-auto px-4 py-8">
41+
<div className="mb-6">
42+
<Link href={`/workspace/${workspaceId}`}>
43+
<Button variant="ghost" size="sm" className="mb-4">
44+
<ArrowLeft className="w-4 h-4 mr-2" />
45+
Back to workspace
46+
</Button>
47+
</Link>
48+
49+
<div className="flex items-center gap-3">
50+
<Database className="w-8 h-8 text-gray-600" />
51+
<h1 className="text-3xl font-bold">Create New Database</h1>
52+
</div>
53+
<p className="text-gray-600 mt-2">
54+
Create a structured database to organize and manage your data
55+
</p>
56+
</div>
57+
58+
<CreateDatabaseDialog
59+
open={showDialog}
60+
onOpenChange={(open) => {
61+
if (!open) handleCancel()
62+
}}
63+
workspaceId={workspaceId}
64+
onCreateDatabase={handleCreateDatabase}
65+
/>
66+
</div>
67+
</div>
68+
)
69+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import React from 'react'
2+
import { redirect } from 'next/navigation'
3+
import { getCurrentUser } from '@/lib/auth'
4+
import { prisma } from '@/lib/db/prisma'
5+
import { generateId } from '@/lib/utils/id'
6+
import CreateDatabasePage from './CreateDatabasePage'
7+
8+
interface CreateDatabasePageProps {
9+
params: Promise<{
10+
workspaceId: string
11+
}>
12+
}
13+
14+
export default async function CreateDatabase({ params }: CreateDatabasePageProps) {
15+
const user = await getCurrentUser()
16+
if (!user) {
17+
redirect('/login')
18+
}
19+
20+
const resolvedParams = await params
21+
22+
// Check if user is a member of the workspace
23+
const workspaceMember = await prisma.workspaceMember.findUnique({
24+
where: {
25+
userId_workspaceId: {
26+
userId: user.id,
27+
workspaceId: resolvedParams.workspaceId
28+
}
29+
}
30+
})
31+
32+
if (!workspaceMember) {
33+
redirect(`/workspace/${resolvedParams.workspaceId}`)
34+
}
35+
36+
const workspace = await prisma.workspace.findUnique({
37+
where: { id: resolvedParams.workspaceId },
38+
select: { id: true, name: true, slug: true }
39+
})
40+
41+
if (!workspace) {
42+
redirect('/')
43+
}
44+
45+
return (
46+
<CreateDatabasePage
47+
workspaceId={resolvedParams.workspaceId}
48+
workspace={workspace}
49+
user={user}
50+
/>
51+
)
52+
}

app/(protected)/workspace/[workspaceId]/page/[pageId]/PageEditorV2.tsx

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import dynamic from 'next/dynamic'
77
import { SnapDocsPageHeader } from '@/components/page/snapdocs-page-header'
88
import { cn } from '@/lib/utils'
99
import toast from 'react-hot-toast'
10-
import { useSocket } from '@/lib/socket/client'
10+
// import { useSocket } from '@/lib/socket/client' // Removed - using Yjs collaboration now
1111

1212
// Dynamically import BlockNoteEditor to avoid SSR hydration issues
1313
const BlockNoteEditor = dynamic(
@@ -65,7 +65,7 @@ interface PageEditorProps {
6565

6666
export default function PageEditorV2({ page, initialContent, user }: PageEditorProps) {
6767
const router = useRouter()
68-
const { isConnected, joinPage, leavePage } = useSocket()
68+
// const { isConnected, joinPage, leavePage } = useSocket() // Removed - using Yjs collaboration now
6969
const [title, setTitle] = useState(page.title || '')
7070
const [icon, setIcon] = useState(page.icon || '')
7171
const [coverImage, setCoverImage] = useState(page.coverImage || '')
@@ -78,22 +78,8 @@ export default function PageEditorV2({ page, initialContent, user }: PageEditorP
7878

7979
const initialBlocks = initialContent?.blocks || []
8080

81-
// Join the page room for real-time collaboration
82-
useEffect(() => {
83-
if (isConnected && user) {
84-
joinPage(page.id, page.workspaceId, {
85-
id: user.id,
86-
name: user.name || 'Anonymous',
87-
email: user.email || '',
88-
avatarUrl: null
89-
})
90-
}
91-
92-
// Clean up when leaving the page
93-
return () => {
94-
leavePage()
95-
}
96-
}, [isConnected, page.id, page.workspaceId, user, joinPage, leavePage])
81+
// Yjs collaboration is now handled directly in BlockNoteEditor component
82+
// No need for Socket.io room joining
9783

9884

9985
// Auto-resize title textarea

app/api/databases/[databaseId]/route.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,71 @@ export async function PUT(
133133
}
134134
}
135135

136+
// PATCH /api/databases/[databaseId] - Partial update database
137+
export async function PATCH(
138+
request: NextRequest,
139+
{ params }: { params: Promise<{ databaseId: string }> }
140+
) {
141+
try {
142+
const session = await getServerSession(authOptions)
143+
if (!session?.user?.id) {
144+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
145+
}
146+
147+
const resolvedParams = await params
148+
const body = await request.json()
149+
150+
// Check database exists and user has access
151+
const database = await prisma.database.findUnique({
152+
where: {
153+
id: resolvedParams.databaseId
154+
},
155+
include: {
156+
workspace: {
157+
include: {
158+
members: {
159+
where: {
160+
userId: session.user.id
161+
}
162+
}
163+
}
164+
}
165+
}
166+
})
167+
168+
if (!database) {
169+
return NextResponse.json({ error: 'Database not found' }, { status: 404 })
170+
}
171+
172+
if (!database.workspace.members.length) {
173+
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
174+
}
175+
176+
const updatedDatabase = await prisma.database.update({
177+
where: {
178+
id: resolvedParams.databaseId
179+
},
180+
data: body,
181+
include: {
182+
views: true,
183+
rows: {
184+
orderBy: {
185+
order: 'asc'
186+
}
187+
}
188+
}
189+
})
190+
191+
return NextResponse.json(updatedDatabase)
192+
} catch (error) {
193+
console.error('Error updating database:', error)
194+
return NextResponse.json(
195+
{ error: 'Internal server error' },
196+
{ status: 500 }
197+
)
198+
}
199+
}
200+
136201
// DELETE /api/databases/[databaseId] - Delete database
137202
export async function DELETE(
138203
request: NextRequest,

app/globals.css

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -126,48 +126,6 @@
126126
@apply relative min-h-full;
127127
}
128128

129-
/* BlockV2 System - Clean hover area and handle positioning */
130-
.block-wrapper {
131-
@apply relative;
132-
}
133-
134-
/* Extend hover area to include the handle space */
135-
.block-wrapper::before {
136-
content: '';
137-
position: absolute;
138-
left: -3rem; /* Mobile: 48px for handles */
139-
right: 0;
140-
top: 0;
141-
bottom: 0;
142-
pointer-events: none;
143-
z-index: 1;
144-
}
145-
146-
@media (min-width: 768px) {
147-
.block-wrapper::before {
148-
left: -4rem; /* Desktop: 64px for handles */
149-
}
150-
}
151-
152-
/* Block content area - properly centered */
153-
.block-content {
154-
@apply relative;
155-
/* Ensure content doesn't shift when handles appear */
156-
margin-left: 0;
157-
}
158-
159-
/* Handle positioning with negative margins - doesn't affect content flow */
160-
.block-wrapper .absolute.-left-12 {
161-
/* Mobile handle position */
162-
left: -3rem;
163-
}
164-
165-
@media (min-width: 768px) {
166-
.block-wrapper .absolute.-left-16 {
167-
/* Desktop handle position */
168-
left: -4rem;
169-
}
170-
}
171129

172130
/* Responsive typography */
173131
@layer utilities {

0 commit comments

Comments
 (0)