Skip to content

Commit 57daef3

Browse files
committed
feat: sync options for google drive in nova (supermemoryai#891)
1 parent c01e3a3 commit 57daef3

1 file changed

Lines changed: 144 additions & 5 deletions

File tree

apps/web/components/add-document/connections.tsx

Lines changed: 144 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,28 @@ import type { ConnectionResponseSchema } from "@repo/validation/api"
66
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
77
import { GoogleDrive, Notion, OneDrive } from "@ui/assets/icons"
88
import { useCustomer } from "autumn-js/react"
9-
import { Check, Loader, Trash2, Zap } from "lucide-react"
9+
import { Check, ChevronDown, Loader, Trash2, Zap } from "lucide-react"
1010
import { useEffect, useState } from "react"
1111
import { toast } from "sonner"
1212
import type { z } from "zod"
1313
import { dmSansClassName } from "@/lib/fonts"
1414
import { cn } from "@lib/utils"
1515
import { Button } from "@ui/components/button"
16+
import {
17+
DropdownMenu,
18+
DropdownMenuContent,
19+
DropdownMenuItem,
20+
DropdownMenuTrigger,
21+
} from "@ui/components/dropdown-menu"
1622
import { RemoveConnectionDialog } from "@/components/remove-connection-dialog"
1723

24+
type GDriveSyncScope = "scoped" | "full"
25+
26+
const GDRIVE_SCOPE_LABELS: Record<GDriveSyncScope, string> = {
27+
scoped: "Files & Folders",
28+
full: "Whole Drive",
29+
}
30+
1831
type Connection = z.infer<typeof ConnectionResponseSchema>
1932

2033
type ConnectorProvider = "google-drive" | "notion" | "onedrive"
@@ -54,6 +67,8 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
5467
const isProUser = hasActivePlan(autumn.customer?.products, "api_pro")
5568
const [connectingProvider, setConnectingProvider] =
5669
useState<ConnectorProvider | null>(null)
70+
const [gdriveSyncScope, setGdriveSyncScope] =
71+
useState<GDriveSyncScope>("scoped")
5772
const [isUpgrading, setIsUpgrading] = useState(false)
5873
const [removeDialog, setRemoveDialog] = useState<{
5974
open: boolean
@@ -114,7 +129,13 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
114129

115130
// Connect mutation
116131
const addConnectionMutation = useMutation({
117-
mutationFn: async (provider: ConnectorProvider) => {
132+
mutationFn: async ({
133+
provider,
134+
syncScope,
135+
}: {
136+
provider: ConnectorProvider
137+
syncScope?: GDriveSyncScope
138+
}) => {
118139
if (!canAddConnection && !isProUser) {
119140
throw new Error(
120141
"Free plan doesn't include connections. Upgrade to Pro for unlimited connections.",
@@ -126,6 +147,10 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
126147
body: {
127148
redirectUrl: window.location.href,
128149
containerTags: [selectedProject],
150+
metadata:
151+
provider === "google-drive" && syncScope === "full"
152+
? { syncScope: "full" }
153+
: undefined,
129154
},
130155
})
131156

@@ -180,7 +205,10 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
180205

181206
const handleConnect = (provider: ConnectorProvider) => {
182207
setConnectingProvider(provider)
183-
addConnectionMutation.mutate(provider)
208+
addConnectionMutation.mutate({
209+
provider,
210+
syncScope: provider === "google-drive" ? gdriveSyncScope : undefined,
211+
})
184212
}
185213

186214
const handleDisconnect = (connection: Connection) => {
@@ -215,7 +243,66 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
215243
const isConnecting =
216244
connectingProvider === provider ||
217245
(addConnectionMutation.isPending &&
218-
addConnectionMutation.variables === provider)
246+
addConnectionMutation.variables?.provider === provider)
247+
248+
if (provider === "google-drive") {
249+
return (
250+
<div
251+
key={provider}
252+
className="bg-[#14161A] border border-[rgba(82,89,102,0.2)] rounded-[12px] flex overflow-hidden"
253+
>
254+
<button
255+
type="button"
256+
onClick={() => handleConnect("google-drive")}
257+
disabled={
258+
!isProUser ||
259+
isConnecting ||
260+
addConnectionMutation.isPending
261+
}
262+
className="flex-1 py-3 flex items-center justify-center gap-2 hover:bg-[#1B1F24] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
263+
>
264+
<Icon className="w-6 h-6 text-[#737373]" />
265+
<p className="text-[14px] font-medium">{config.title}</p>
266+
{isConnecting && (
267+
<Loader className="h-4 w-4 animate-spin text-[#4BA0FA]" />
268+
)}
269+
</button>
270+
<div className="w-px bg-[rgba(82,89,102,0.4)] self-stretch my-2" />
271+
<DropdownMenu>
272+
<DropdownMenuTrigger asChild>
273+
<button
274+
type="button"
275+
className="w-8 flex items-center justify-center hover:bg-[#1B1F24] transition-colors"
276+
>
277+
<ChevronDown className="w-3 h-3 text-[#737373]" />
278+
</button>
279+
</DropdownMenuTrigger>
280+
<DropdownMenuContent align="end" className="w-40">
281+
{(
282+
Object.entries(GDRIVE_SCOPE_LABELS) as [
283+
GDriveSyncScope,
284+
string,
285+
][]
286+
).map(([scope, label]) => (
287+
<DropdownMenuItem
288+
key={scope}
289+
onClick={(e) => {
290+
e.stopPropagation()
291+
setGdriveSyncScope(scope)
292+
}}
293+
className="flex items-center justify-between"
294+
>
295+
{label}
296+
{gdriveSyncScope === scope && (
297+
<Check className="w-3 h-3 text-[#4BA0FA]" />
298+
)}
299+
</DropdownMenuItem>
300+
))}
301+
</DropdownMenuContent>
302+
</DropdownMenu>
303+
</div>
304+
)
305+
}
219306

220307
return (
221308
<button
@@ -249,7 +336,7 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
249336
const isConnecting =
250337
connectingProvider === provider ||
251338
(addConnectionMutation.isPending &&
252-
addConnectionMutation.variables === provider)
339+
addConnectionMutation.variables?.provider === provider)
253340

254341
return (
255342
<div
@@ -285,6 +372,58 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
285372
>
286373
<Trash2 className="h-4 w-4" />
287374
</Button>
375+
) : provider === "google-drive" ? (
376+
<div className="flex items-center rounded-md overflow-hidden">
377+
<button
378+
type="button"
379+
onClick={() => handleConnect("google-drive")}
380+
disabled={
381+
!isProUser ||
382+
isConnecting ||
383+
addConnectionMutation.isPending
384+
}
385+
className="bg-[#4BA0FA] text-black hover:bg-[#4BA0FA]/90 text-[14px] font-medium px-3 h-8 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
386+
>
387+
{isConnecting ? (
388+
<Loader className="h-4 w-4 animate-spin" />
389+
) : (
390+
"Connect"
391+
)}
392+
</button>
393+
<div className="w-px h-5 bg-black/20" />
394+
<DropdownMenu>
395+
<DropdownMenuTrigger asChild>
396+
<button
397+
type="button"
398+
className="bg-[#4BA0FA] text-black hover:bg-[#4BA0FA]/90 px-1.5 h-8 flex items-center transition-colors"
399+
>
400+
<ChevronDown className="w-3 h-3" />
401+
</button>
402+
</DropdownMenuTrigger>
403+
<DropdownMenuContent align="end" className="w-40">
404+
{(
405+
Object.entries(GDRIVE_SCOPE_LABELS) as [
406+
GDriveSyncScope,
407+
string,
408+
][]
409+
).map(([scope, label]) => (
410+
<DropdownMenuItem
411+
key={scope}
412+
onClick={(e) => {
413+
e.stopPropagation()
414+
setGdriveSyncScope(scope)
415+
}}
416+
className="flex items-center justify-between"
417+
>
418+
{label}
419+
{gdriveSyncScope === scope && (
420+
<Check className="w-3 h-3 text-[#4BA0FA]" />
421+
)}
422+
</DropdownMenuItem>
423+
))}
424+
</DropdownMenuContent>
425+
</DropdownMenu>
426+
</div>
288427
) : (
289428
<Button
290429
onClick={() =>

0 commit comments

Comments
 (0)