ultisuite-client/components/gmail/settings/automation/api-token-drive-scope-editor.tsx
R3D347HR4Y 636b8cf789
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
Lots of changes to the API and webhooks
2026-06-07 16:02:55 +02:00

166 lines
5.9 KiB
TypeScript

"use client"
import { useMemo, useState } from "react"
import { ChevronRight, Folder, Plus, X } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import { Label } from "@/components/ui/label"
import { useDriveList } from "@/lib/api/hooks/use-drive-queries"
import type { ApiTokenDriveScope } from "@/lib/api/types"
import { displayFileName } from "@/lib/drive/display-file-name"
import { normalizeDriveFolderPath } from "@/lib/drive/drive-sidebar-tree"
import { AutomationBorderedFieldset } from "@/components/gmail/settings/automation/automation-bordered-fieldset"
import { cn } from "@/lib/utils"
export function ApiTokenDriveScopeEditor({
scope,
onChange,
enabled,
className,
}: {
scope: ApiTokenDriveScope
onChange: (scope: ApiTokenDriveScope) => void
enabled: boolean
className?: string
}) {
const [browsePath, setBrowsePath] = useState("/")
const list = useDriveList(browsePath, 1, "", enabled && !scope.all_folders)
const folders = useMemo(
() => (list.data?.files ?? []).filter((f) => f.type === "directory"),
[list.data?.files]
)
const crumbs = useMemo(() => {
const normalized = normalizeDriveFolderPath(browsePath)
if (normalized === "/") return [{ path: "/", label: "Mon Drive" }]
const parts = normalized.slice(1).split("/")
const out: { path: string; label: string }[] = [{ path: "/", label: "Mon Drive" }]
for (let i = 0; i < parts.length; i++) {
const path = "/" + parts.slice(0, i + 1).join("/")
out.push({ path, label: displayFileName(parts[i]!) })
}
return out
}, [browsePath])
if (!enabled) return null
function addFolder(path: string) {
const normalized = normalizeDriveFolderPath(path)
if (scope.folder_paths.includes(normalized)) return
onChange({
all_folders: false,
folder_paths: [...scope.folder_paths, normalized],
})
}
function removeFolder(path: string) {
onChange({
all_folders: false,
folder_paths: scope.folder_paths.filter((p) => p !== path),
})
}
return (
<AutomationBorderedFieldset
className={className}
legend="Périmètre Drive — dossiers"
>
<label className="flex items-start gap-2">
<Checkbox
checked={scope.all_folders}
onCheckedChange={(checked) =>
onChange({
all_folders: checked === true,
folder_paths: checked === true ? [] : scope.folder_paths,
})
}
className="mt-0.5"
/>
<span className="text-sm">
Tout le Drive
<span className="mt-0.5 block text-xs text-muted-foreground">
Accès à l&apos;intégralité de l&apos;arborescence.
</span>
</span>
</label>
{!scope.all_folders && (
<div className="space-y-3">
{scope.folder_paths.length > 0 && (
<div className="space-y-1.5">
<Label className="text-xs text-muted-foreground">Dossiers autorisés</Label>
<ul className="space-y-1">
{scope.folder_paths.map((path) => (
<li
key={path}
className="flex items-center justify-between gap-2 rounded-md border border-border px-2.5 py-1.5 text-sm"
>
<span className="truncate font-mono text-xs">{path}</span>
<Button
type="button"
variant="ghost"
size="icon"
className="size-7 shrink-0"
onClick={() => removeFolder(path)}
aria-label={`Retirer ${path}`}
>
<X className="size-3.5" />
</Button>
</li>
))}
</ul>
</div>
)}
<div className="space-y-2 rounded-md border border-dashed border-border p-2.5">
<Label className="text-xs text-muted-foreground">Ajouter un dossier</Label>
<div className="flex flex-wrap items-center gap-1 text-xs text-muted-foreground">
{crumbs.map((crumb, index) => (
<span key={crumb.path} className="inline-flex items-center gap-1">
{index > 0 && <ChevronRight className="size-3" />}
<button
type="button"
className="rounded px-1 hover:bg-muted hover:text-foreground"
onClick={() => setBrowsePath(crumb.path)}
>
{crumb.label}
</button>
</span>
))}
</div>
<Button
type="button"
variant="outline"
size="sm"
className="w-full justify-start"
onClick={() => addFolder(browsePath)}
>
<Plus className="mr-1.5 size-3.5" />
Autoriser « {crumbs[crumbs.length - 1]?.label ?? "Mon Drive"} »
</Button>
{list.isLoading ? (
<p className="text-xs text-muted-foreground">Chargement</p>
) : folders.length > 0 ? (
<ul className="max-h-36 space-y-1 overflow-y-auto">
{folders.map((folder) => (
<li key={folder.path}>
<button
type="button"
className="flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-sm hover:bg-muted/50"
onClick={() => setBrowsePath(folder.path)}
>
<Folder className="size-4 shrink-0 text-muted-foreground" />
<span className="truncate">{displayFileName(folder.name)}</span>
</button>
</li>
))}
</ul>
) : null}
</div>
</div>
)}
</AutomationBorderedFieldset>
)
}